[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-font-families-controller.php (source)

   1  <?php
   2  /**
   3   * REST API: WP_REST_Font_Families_Controller class
   4   *
   5   * @package WordPress
   6   * @subpackage REST_API
   7   * @since 6.5.0
   8   */
   9  
  10  /**
  11   * Font Families Controller class.
  12   *
  13   * @since 6.5.0
  14   */
  15  class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller {
  16  
  17      /**
  18       * The latest version of theme.json schema supported by the controller.
  19       *
  20       * @since 6.5.0
  21       * @var int
  22       */
  23      const LATEST_THEME_JSON_VERSION_SUPPORTED = 3;
  24  
  25      /**
  26       * Whether the controller supports batching.
  27       *
  28       * @since 6.5.0
  29       * @var false
  30       */
  31      protected $allow_batch = false;
  32  
  33      /**
  34       * Checks if a given request has access to font families.
  35       *
  36       * @since 6.5.0
  37       *
  38       * @param WP_REST_Request $request Full details about the request.
  39       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  40       */
  41  	public function get_items_permissions_check( $request ) {
  42          $post_type = get_post_type_object( $this->post_type );
  43  
  44          if ( ! current_user_can( $post_type->cap->read ) ) {
  45              return new WP_Error(
  46                  'rest_cannot_read',
  47                  __( 'Sorry, you are not allowed to access font families.' ),
  48                  array( 'status' => rest_authorization_required_code() )
  49              );
  50          }
  51  
  52          return true;
  53      }
  54  
  55      /**
  56       * Checks if a given request has access to a font family.
  57       *
  58       * @since 6.5.0
  59       *
  60       * @param WP_REST_Request $request Full details about the request.
  61       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  62       */
  63  	public function get_item_permissions_check( $request ) {
  64          $post = $this->get_post( $request['id'] );
  65          if ( is_wp_error( $post ) ) {
  66              return $post;
  67          }
  68  
  69          if ( ! current_user_can( 'read_post', $post->ID ) ) {
  70              return new WP_Error(
  71                  'rest_cannot_read',
  72                  __( 'Sorry, you are not allowed to access this font family.' ),
  73                  array( 'status' => rest_authorization_required_code() )
  74              );
  75          }
  76  
  77          return true;
  78      }
  79  
  80      /**
  81       * Validates settings when creating or updating a font family.
  82       *
  83       * @since 6.5.0
  84       *
  85       * @param string          $value   Encoded JSON string of font family settings.
  86       * @param WP_REST_Request $request Request object.
  87       * @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
  88       */
  89  	public function validate_font_family_settings( $value, $request ) {
  90          // Enforce JSON Schema validity for field before applying custom validation logic.
  91          $args     = $this->get_endpoint_args_for_item_schema( $request->get_method() );
  92          $validity = rest_validate_value_from_schema( $value, $args['font_family_settings'], 'font_family_settings' );
  93  
  94          if ( is_wp_error( $validity ) ) {
  95              return $validity;
  96          }
  97  
  98          $settings = json_decode( $value, true );
  99  
 100          // Check settings string is valid JSON.
 101          if ( null === $settings ) {
 102              return new WP_Error(
 103                  'rest_invalid_param',
 104                  /* translators: %s: Parameter name: "font_family_settings". */
 105                  sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ),
 106                  array( 'status' => 400 )
 107              );
 108          }
 109  
 110          $schema   = $this->get_item_schema()['properties']['font_family_settings'];
 111          $required = $schema['required'];
 112  
 113          if ( isset( $request['id'] ) ) {
 114              // Allow sending individual properties if we are updating an existing font family.
 115              unset( $schema['required'] );
 116  
 117              // But don't allow updating the slug, since it is used as a unique identifier.
 118              if ( isset( $settings['slug'] ) ) {
 119                  return new WP_Error(
 120                      'rest_invalid_param',
 121                      /* translators: %s: Name of parameter being updated: font_family_settings[slug]". */
 122                      sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ),
 123                      array( 'status' => 400 )
 124                  );
 125              }
 126          }
 127  
 128          // Check that the font face settings match the theme.json schema.
 129          $has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' );
 130  
 131          if ( is_wp_error( $has_valid_settings ) ) {
 132              $has_valid_settings->add_data( array( 'status' => 400 ) );
 133              return $has_valid_settings;
 134          }
 135  
 136          // Check that none of the required settings are empty values.
 137          foreach ( $required as $key ) {
 138              if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) {
 139                  return new WP_Error(
 140                      'rest_invalid_param',
 141                      /* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */
 142                      sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ),
 143                      array( 'status' => 400 )
 144                  );
 145              }
 146          }
 147  
 148          return true;
 149      }
 150  
 151      /**
 152       * Sanitizes the font family settings when creating or updating a font family.
 153       *
 154       * @since 6.5.0
 155       *
 156       * @param string $value Encoded JSON string of font family settings.
 157       * @return array Decoded array of font family settings.
 158       */
 159  	public function sanitize_font_family_settings( $value ) {
 160          // Settings arrive as stringified JSON, since this is a multipart/form-data request.
 161          $settings = json_decode( $value, true );
 162          $schema   = $this->get_item_schema()['properties']['font_family_settings']['properties'];
 163  
 164          // Sanitize settings based on callbacks in the schema.
 165          foreach ( $settings as $key => $value ) {
 166              $sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
 167              $settings[ $key ]  = call_user_func( $sanitize_callback, $value );
 168          }
 169  
 170          return $settings;
 171      }
 172  
 173      /**
 174       * Creates a single font family.
 175       *
 176       * @since 6.5.0
 177       *
 178       * @param WP_REST_Request $request Full details about the request.
 179       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 180       */
 181  	public function create_item( $request ) {
 182          $settings = $request->get_param( 'font_family_settings' );
 183  
 184          // Check that the font family slug is unique.
 185          $query = new WP_Query(
 186              array(
 187                  'post_type'              => $this->post_type,
 188                  'posts_per_page'         => 1,
 189                  'name'                   => $settings['slug'],
 190                  'update_post_meta_cache' => false,
 191                  'update_post_term_cache' => false,
 192              )
 193          );
 194          if ( ! empty( $query->posts ) ) {
 195              return new WP_Error(
 196                  'rest_duplicate_font_family',
 197                  /* translators: %s: Font family slug. */
 198                  sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ),
 199                  array( 'status' => 400 )
 200              );
 201          }
 202  
 203          return parent::create_item( $request );
 204      }
 205  
 206      /**
 207       * Deletes a single font family.
 208       *
 209       * @since 6.5.0
 210       *
 211       * @param WP_REST_Request $request Full details about the request.
 212       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 213       */
 214  	public function delete_item( $request ) {
 215          $force = isset( $request['force'] ) ? (bool) $request['force'] : false;
 216  
 217          // We don't support trashing for font families.
 218          if ( ! $force ) {
 219              return new WP_Error(
 220                  'rest_trash_not_supported',
 221                  /* translators: %s: force=true */
 222                  sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ),
 223                  array( 'status' => 501 )
 224              );
 225          }
 226  
 227          return parent::delete_item( $request );
 228      }
 229  
 230      /**
 231       * Prepares a single font family output for response.
 232       *
 233       * @since 6.5.0
 234       *
 235       * @param WP_Post         $item    Post object.
 236       * @param WP_REST_Request $request Request object.
 237       * @return WP_REST_Response Response object.
 238       */
 239  	public function prepare_item_for_response( $item, $request ) {
 240          $fields = $this->get_fields_for_response( $request );
 241          $data   = array();
 242  
 243          if ( rest_is_field_included( 'id', $fields ) ) {
 244              $data['id'] = $item->ID;
 245          }
 246  
 247          if ( rest_is_field_included( 'theme_json_version', $fields ) ) {
 248              $data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED;
 249          }
 250  
 251          if ( rest_is_field_included( 'font_faces', $fields ) ) {
 252              $data['font_faces'] = $this->get_font_face_ids( $item->ID );
 253          }
 254  
 255          if ( rest_is_field_included( 'font_family_settings', $fields ) ) {
 256              $data['font_family_settings'] = $this->get_settings_from_post( $item );
 257          }
 258  
 259          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 260          $data    = $this->add_additional_fields_to_object( $data, $request );
 261          $data    = $this->filter_response_by_context( $data, $context );
 262  
 263          $response = rest_ensure_response( $data );
 264  
 265          if ( rest_is_field_included( '_links', $fields ) ) {
 266              $links = $this->prepare_links( $item );
 267              $response->add_links( $links );
 268          }
 269  
 270          /**
 271           * Filters the font family data for a REST API response.
 272           *
 273           * @since 6.5.0
 274           *
 275           * @param WP_REST_Response $response The response object.
 276           * @param WP_Post          $post     Font family post object.
 277           * @param WP_REST_Request  $request  Request object.
 278           */
 279          return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request );
 280      }
 281  
 282      /**
 283       * Retrieves the post's schema, conforming to JSON Schema.
 284       *
 285       * @since 6.5.0
 286       *
 287       * @return array Item schema data.
 288       */
 289  	public function get_item_schema() {
 290          if ( $this->schema ) {
 291              return $this->add_additional_fields_schema( $this->schema );
 292          }
 293  
 294          $schema = array(
 295              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 296              'title'      => $this->post_type,
 297              'type'       => 'object',
 298              // Base properties for every Post.
 299              'properties' => array(
 300                  'id'                   => array(
 301                      'description' => __( 'Unique identifier for the post.', 'default' ),
 302                      'type'        => 'integer',
 303                      'context'     => array( 'view', 'edit', 'embed' ),
 304                      'readonly'    => true,
 305                  ),
 306                  'theme_json_version'   => array(
 307                      'description' => __( 'Version of the theme.json schema used for the typography settings.' ),
 308                      'type'        => 'integer',
 309                      'default'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
 310                      'minimum'     => 2,
 311                      'maximum'     => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
 312                      'context'     => array( 'view', 'edit', 'embed' ),
 313                  ),
 314                  'font_faces'           => array(
 315                      'description' => __( 'The IDs of the child font faces in the font family.' ),
 316                      'type'        => 'array',
 317                      'context'     => array( 'view', 'edit', 'embed' ),
 318                      'items'       => array(
 319                          'type' => 'integer',
 320                      ),
 321                  ),
 322                  // Font family settings come directly from theme.json schema
 323                  // See https://schemas.wp.org/trunk/theme.json
 324                  'font_family_settings' => array(
 325                      'description'          => __( 'font-face definition in theme.json format.' ),
 326                      'type'                 => 'object',
 327                      'context'              => array( 'view', 'edit', 'embed' ),
 328                      'properties'           => array(
 329                          'name'       => array(
 330                              'description' => __( 'Name of the font family preset, translatable.' ),
 331                              'type'        => 'string',
 332                              'arg_options' => array(
 333                                  'sanitize_callback' => 'sanitize_text_field',
 334                              ),
 335                          ),
 336                          'slug'       => array(
 337                              'description' => __( 'Kebab-case unique identifier for the font family preset.' ),
 338                              'type'        => 'string',
 339                              'arg_options' => array(
 340                                  'sanitize_callback' => 'sanitize_title',
 341                              ),
 342                          ),
 343                          'fontFamily' => array(
 344                              'description' => __( 'CSS font-family value.' ),
 345                              'type'        => 'string',
 346                              'arg_options' => array(
 347                                  'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
 348                              ),
 349                          ),
 350                          'preview'    => array(
 351                              'description' => __( 'URL to a preview image of the font family.' ),
 352                              'type'        => 'string',
 353                              'format'      => 'uri',
 354                              'default'     => '',
 355                              'arg_options' => array(
 356                                  'sanitize_callback' => 'sanitize_url',
 357                              ),
 358                          ),
 359                      ),
 360                      'required'             => array( 'name', 'slug', 'fontFamily' ),
 361                      'additionalProperties' => false,
 362                  ),
 363              ),
 364          );
 365  
 366          $this->schema = $schema;
 367  
 368          return $this->add_additional_fields_schema( $this->schema );
 369      }
 370  
 371      /**
 372       * Retrieves the item's schema for display / public consumption purposes.
 373       *
 374       * @since 6.5.0
 375       *
 376       * @return array Public item schema data.
 377       */
 378  	public function get_public_item_schema() {
 379  
 380          $schema = parent::get_public_item_schema();
 381  
 382          // Also remove `arg_options' from child font_family_settings properties, since the parent
 383          // controller only handles the top level properties.
 384          foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) {
 385              unset( $property['arg_options'] );
 386          }
 387  
 388          return $schema;
 389      }
 390  
 391      /**
 392       * Retrieves the query params for the font family collection.
 393       *
 394       * @since 6.5.0
 395       *
 396       * @return array Collection parameters.
 397       */
 398  	public function get_collection_params() {
 399          $query_params = parent::get_collection_params();
 400  
 401          // Remove unneeded params.
 402          unset(
 403              $query_params['after'],
 404              $query_params['modified_after'],
 405              $query_params['before'],
 406              $query_params['modified_before'],
 407              $query_params['search'],
 408              $query_params['search_columns'],
 409              $query_params['status']
 410          );
 411  
 412          $query_params['orderby']['default'] = 'id';
 413          $query_params['orderby']['enum']    = array( 'id', 'include' );
 414  
 415          /**
 416           * Filters collection parameters for the font family controller.
 417           *
 418           * @since 6.5.0
 419           *
 420           * @param array $query_params JSON Schema-formatted collection parameters.
 421           */
 422          return apply_filters( 'rest_wp_font_family_collection_params', $query_params );
 423      }
 424  
 425      /**
 426       * Get the arguments used when creating or updating a font family.
 427       *
 428       * @since 6.5.0
 429       *
 430       * @return array Font family create/edit arguments.
 431       */
 432  	public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
 433          if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) {
 434              $properties = $this->get_item_schema()['properties'];
 435              return array(
 436                  'theme_json_version'   => $properties['theme_json_version'],
 437                  // When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data.
 438                  // Font families don't currently support file uploads, but may accept preview files in the future.
 439                  'font_family_settings' => array(
 440                      'description'       => __( 'font-family declaration in theme.json format, encoded as a string.' ),
 441                      'type'              => 'string',
 442                      'required'          => true,
 443                      'validate_callback' => array( $this, 'validate_font_family_settings' ),
 444                      'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ),
 445                  ),
 446              );
 447          }
 448  
 449          return parent::get_endpoint_args_for_item_schema( $method );
 450      }
 451  
 452      /**
 453       * Get the child font face post IDs.
 454       *
 455       * @since 6.5.0
 456       *
 457       * @param int $font_family_id Font family post ID.
 458       * @return int[] Array of child font face post IDs.
 459       */
 460  	protected function get_font_face_ids( $font_family_id ) {
 461          $query = new WP_Query(
 462              array(
 463                  'fields'                 => 'ids',
 464                  'post_parent'            => $font_family_id,
 465                  'post_type'              => 'wp_font_face',
 466                  'posts_per_page'         => 99,
 467                  'order'                  => 'ASC',
 468                  'orderby'                => 'id',
 469                  'update_post_meta_cache' => false,
 470                  'update_post_term_cache' => false,
 471              )
 472          );
 473  
 474          return $query->posts;
 475      }
 476  
 477      /**
 478       * Prepares font family links for the request.
 479       *
 480       * @since 6.5.0
 481       *
 482       * @param WP_Post $post Post object.
 483       * @return array Links for the given post.
 484       */
 485  	protected function prepare_links( $post ) {
 486          // Entity meta.
 487          $links = parent::prepare_links( $post );
 488  
 489          return array(
 490              'self'       => $links['self'],
 491              'collection' => $links['collection'],
 492              'font_faces' => $this->prepare_font_face_links( $post->ID ),
 493          );
 494      }
 495  
 496      /**
 497       * Prepares child font face links for the request.
 498       *
 499       * @param int $font_family_id Font family post ID.
 500       * @return array Links for the child font face posts.
 501       */
 502  	protected function prepare_font_face_links( $font_family_id ) {
 503          $font_face_ids = $this->get_font_face_ids( $font_family_id );
 504          $links         = array();
 505          foreach ( $font_face_ids as $font_face_id ) {
 506              $links[] = array(
 507                  'embeddable' => true,
 508                  'href'       => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ),
 509              );
 510          }
 511          return $links;
 512      }
 513  
 514      /**
 515       * Prepares a single font family post for create or update.
 516       *
 517       * @since 6.5.0
 518       *
 519       * @param WP_REST_Request $request Request object.
 520       * @return stdClass|WP_Error Post object or WP_Error.
 521       */
 522  	protected function prepare_item_for_database( $request ) {
 523          $prepared_post = new stdClass();
 524          // Settings have already been decoded by ::sanitize_font_family_settings().
 525          $settings = $request->get_param( 'font_family_settings' );
 526  
 527          // This is an update and we merge with the existing font family.
 528          if ( isset( $request['id'] ) ) {
 529              $existing_post = $this->get_post( $request['id'] );
 530              if ( is_wp_error( $existing_post ) ) {
 531                  return $existing_post;
 532              }
 533  
 534              $prepared_post->ID = $existing_post->ID;
 535              $existing_settings = $this->get_settings_from_post( $existing_post );
 536              $settings          = array_merge( $existing_settings, $settings );
 537          }
 538  
 539          $prepared_post->post_type   = $this->post_type;
 540          $prepared_post->post_status = 'publish';
 541          $prepared_post->post_title  = $settings['name'];
 542          $prepared_post->post_name   = sanitize_title( $settings['slug'] );
 543  
 544          // Remove duplicate information from settings.
 545          unset( $settings['name'] );
 546          unset( $settings['slug'] );
 547  
 548          $prepared_post->post_content = wp_json_encode( $settings );
 549  
 550          return $prepared_post;
 551      }
 552  
 553      /**
 554       * Gets the font family's settings from the post.
 555       *
 556       * @since 6.5.0
 557       *
 558       * @param WP_Post $post Font family post object.
 559       * @return array Font family settings array.
 560       */
 561  	protected function get_settings_from_post( $post ) {
 562          $settings_json = json_decode( $post->post_content, true );
 563  
 564          // Default to empty strings if the settings are missing.
 565          return array(
 566              'name'       => isset( $post->post_title ) && $post->post_title ? $post->post_title : '',
 567              'slug'       => isset( $post->post_name ) && $post->post_name ? $post->post_name : '',
 568              'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '',
 569              'preview'    => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '',
 570          );
 571      }
 572  }


Generated : Tue Jun 16 08:20:09 2026 Cross-referenced by PHPXref