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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref