[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-global-styles-controller.php (source)

   1  <?php
   2  /**
   3   * REST API: WP_REST_Global_Styles_Controller class
   4   *
   5   * @package    WordPress
   6   * @subpackage REST_API
   7   * @since 5.9.0
   8   */
   9  
  10  /**
  11   * Base Global Styles REST API Controller.
  12   */
  13  class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
  14  
  15      /**
  16       * Post type.
  17       *
  18       * @since 5.9.0
  19       * @var string
  20       */
  21      protected $post_type;
  22  
  23      /**
  24       * Constructor.
  25       * @since 5.9.0
  26       */
  27  	public function __construct() {
  28          $this->namespace = 'wp/v2';
  29          $this->rest_base = 'global-styles';
  30          $this->post_type = 'wp_global_styles';
  31      }
  32  
  33      /**
  34       * Registers the controllers routes.
  35       *
  36       * @since 5.9.0
  37       *
  38       * @return void
  39       */
  40  	public function register_routes() {
  41          register_rest_route(
  42              $this->namespace,
  43              '/' . $this->rest_base . '/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)/variations',
  44              array(
  45                  array(
  46                      'methods'             => WP_REST_Server::READABLE,
  47                      'callback'            => array( $this, 'get_theme_items' ),
  48                      'permission_callback' => array( $this, 'get_theme_items_permissions_check' ),
  49                      'args'                => array(
  50                          'stylesheet' => array(
  51                              'description' => __( 'The theme identifier' ),
  52                              'type'        => 'string',
  53                          ),
  54                      ),
  55                  ),
  56              )
  57          );
  58  
  59          // List themes global styles.
  60          register_rest_route(
  61              $this->namespace,
  62              // The route.
  63              sprintf(
  64                  '/%s/themes/(?P<stylesheet>%s)',
  65                  $this->rest_base,
  66                  // Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
  67                  // Excludes invalid directory name characters: `/:<>*?"|`.
  68                  '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'
  69              ),
  70              array(
  71                  array(
  72                      'methods'             => WP_REST_Server::READABLE,
  73                      'callback'            => array( $this, 'get_theme_item' ),
  74                      'permission_callback' => array( $this, 'get_theme_item_permissions_check' ),
  75                      'args'                => array(
  76                          'stylesheet' => array(
  77                              'description'       => __( 'The theme identifier' ),
  78                              'type'              => 'string',
  79                              'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
  80                          ),
  81                      ),
  82                  ),
  83              )
  84          );
  85  
  86          // Lists/updates a single global style variation based on the given id.
  87          register_rest_route(
  88              $this->namespace,
  89              '/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
  90              array(
  91                  array(
  92                      'methods'             => WP_REST_Server::READABLE,
  93                      'callback'            => array( $this, 'get_item' ),
  94                      'permission_callback' => array( $this, 'get_item_permissions_check' ),
  95                      'args'                => array(
  96                          'id' => array(
  97                              'description'       => __( 'The id of a template' ),
  98                              'type'              => 'string',
  99                              'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
 100                          ),
 101                      ),
 102                  ),
 103                  array(
 104                      'methods'             => WP_REST_Server::EDITABLE,
 105                      'callback'            => array( $this, 'update_item' ),
 106                      'permission_callback' => array( $this, 'update_item_permissions_check' ),
 107                      'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
 108                  ),
 109                  'schema' => array( $this, 'get_public_item_schema' ),
 110              )
 111          );
 112      }
 113  
 114      /**
 115       * Sanitize the global styles ID or stylesheet to decode endpoint.
 116       * For example, `wp/v2/global-styles/twentytwentytwo%200.4.0`
 117       * would be decoded to `twentytwentytwo 0.4.0`.
 118       *
 119       * @since 5.9.0
 120       *
 121       * @param string $id_or_stylesheet Global styles ID or stylesheet.
 122       * @return string Sanitized global styles ID or stylesheet.
 123       */
 124  	public function _sanitize_global_styles_callback( $id_or_stylesheet ) {
 125          return urldecode( $id_or_stylesheet );
 126      }
 127  
 128      /**
 129       * Checks if a given request has access to read a single global style.
 130       *
 131       * @since 5.9.0
 132       *
 133       * @param WP_REST_Request $request Full details about the request.
 134       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
 135       */
 136  	public function get_item_permissions_check( $request ) {
 137          $post = $this->get_post( $request['id'] );
 138          if ( is_wp_error( $post ) ) {
 139              return $post;
 140          }
 141  
 142          if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
 143              return new WP_Error(
 144                  'rest_forbidden_context',
 145                  __( 'Sorry, you are not allowed to edit this global style.' ),
 146                  array( 'status' => rest_authorization_required_code() )
 147              );
 148          }
 149  
 150          if ( ! $this->check_read_permission( $post ) ) {
 151              return new WP_Error(
 152                  'rest_cannot_view',
 153                  __( 'Sorry, you are not allowed to view this global style.' ),
 154                  array( 'status' => rest_authorization_required_code() )
 155              );
 156          }
 157  
 158          return true;
 159      }
 160  
 161      /**
 162       * Checks if a global style can be read.
 163       *
 164       * @since 5.9.0
 165       *
 166       * @param WP_Post $post Post object.
 167       * @return bool Whether the post can be read.
 168       */
 169  	protected function check_read_permission( $post ) {
 170          return current_user_can( 'read_post', $post->ID );
 171      }
 172  
 173      /**
 174       * Returns the given global styles config.
 175       *
 176       * @since 5.9.0
 177       *
 178       * @param WP_REST_Request $request The request instance.
 179       *
 180       * @return WP_REST_Response|WP_Error
 181       */
 182  	public function get_item( $request ) {
 183          $post = $this->get_post( $request['id'] );
 184          if ( is_wp_error( $post ) ) {
 185              return $post;
 186          }
 187  
 188          return $this->prepare_item_for_response( $post, $request );
 189      }
 190  
 191      /**
 192       * Checks if a given request has access to write a single global styles config.
 193       *
 194       * @since 5.9.0
 195       *
 196       * @param WP_REST_Request $request Full details about the request.
 197       * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
 198       */
 199  	public function update_item_permissions_check( $request ) {
 200          $post = $this->get_post( $request['id'] );
 201          if ( is_wp_error( $post ) ) {
 202              return $post;
 203          }
 204  
 205          if ( $post && ! $this->check_update_permission( $post ) ) {
 206              return new WP_Error(
 207                  'rest_cannot_edit',
 208                  __( 'Sorry, you are not allowed to edit this global style.' ),
 209                  array( 'status' => rest_authorization_required_code() )
 210              );
 211          }
 212  
 213          return true;
 214      }
 215  
 216      /**
 217       * Checks if a global style can be edited.
 218       *
 219       * @since 5.9.0
 220       *
 221       * @param WP_Post $post Post object.
 222       * @return bool Whether the post can be edited.
 223       */
 224  	protected function check_update_permission( $post ) {
 225          return current_user_can( 'edit_post', $post->ID );
 226      }
 227  
 228      /**
 229       * Updates a single global style config.
 230       *
 231       * @since 5.9.0
 232       *
 233       * @param WP_REST_Request $request Full details about the request.
 234       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 235       */
 236  	public function update_item( $request ) {
 237          $post_before = $this->get_post( $request['id'] );
 238          if ( is_wp_error( $post_before ) ) {
 239              return $post_before;
 240          }
 241  
 242          $changes = $this->prepare_item_for_database( $request );
 243          $result  = wp_update_post( wp_slash( (array) $changes ), true, false );
 244          if ( is_wp_error( $result ) ) {
 245              return $result;
 246          }
 247  
 248          $post          = get_post( $request['id'] );
 249          $fields_update = $this->update_additional_fields_for_object( $post, $request );
 250          if ( is_wp_error( $fields_update ) ) {
 251              return $fields_update;
 252          }
 253  
 254          wp_after_insert_post( $post, true, $post_before );
 255  
 256          $response = $this->prepare_item_for_response( $post, $request );
 257  
 258          return rest_ensure_response( $response );
 259      }
 260  
 261      /**
 262       * Prepares a single global styles config for update.
 263       *
 264       * @since 5.9.0
 265       *
 266       * @param WP_REST_Request $request Request object.
 267       * @return stdClass Changes to pass to wp_update_post.
 268       */
 269  	protected function prepare_item_for_database( $request ) {
 270          $changes     = new stdClass();
 271          $changes->ID = $request['id'];
 272  
 273          $post            = get_post( $request['id'] );
 274          $existing_config = array();
 275          if ( $post ) {
 276              $existing_config     = json_decode( $post->post_content, true );
 277              $json_decoding_error = json_last_error();
 278              if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
 279                  ! $existing_config['isGlobalStylesUserThemeJSON'] ) {
 280                  $existing_config = array();
 281              }
 282          }
 283  
 284          if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
 285              $config = array();
 286              if ( isset( $request['styles'] ) ) {
 287                  $config['styles'] = $request['styles'];
 288              } elseif ( isset( $existing_config['styles'] ) ) {
 289                  $config['styles'] = $existing_config['styles'];
 290              }
 291              if ( isset( $request['settings'] ) ) {
 292                  $config['settings'] = $request['settings'];
 293              } elseif ( isset( $existing_config['settings'] ) ) {
 294                  $config['settings'] = $existing_config['settings'];
 295              }
 296              $config['isGlobalStylesUserThemeJSON'] = true;
 297              $config['version']                     = WP_Theme_JSON::LATEST_SCHEMA;
 298              $changes->post_content                 = wp_json_encode( $config );
 299          }
 300  
 301          // Post title.
 302          if ( isset( $request['title'] ) ) {
 303              if ( is_string( $request['title'] ) ) {
 304                  $changes->post_title = $request['title'];
 305              } elseif ( ! empty( $request['title']['raw'] ) ) {
 306                  $changes->post_title = $request['title']['raw'];
 307              }
 308          }
 309  
 310          return $changes;
 311      }
 312  
 313      /**
 314       * Prepare a global styles config output for response.
 315       *
 316       * @since 5.9.0
 317       *
 318       * @param WP_Post         $post    Global Styles post object.
 319       * @param WP_REST_Request $request Request object.
 320       * @return WP_REST_Response Response object.
 321       */
 322  	public function prepare_item_for_response( $post, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
 323          $raw_config                       = json_decode( $post->post_content, true );
 324          $is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];
 325          $config                           = array();
 326          if ( $is_global_styles_user_theme_json ) {
 327              $config = ( new WP_Theme_JSON( $raw_config, 'custom' ) )->get_raw_data();
 328          }
 329  
 330          // Base fields for every post.
 331          $data   = array();
 332          $fields = $this->get_fields_for_response( $request );
 333  
 334          if ( rest_is_field_included( 'id', $fields ) ) {
 335              $data['id'] = $post->ID;
 336          }
 337  
 338          if ( rest_is_field_included( 'title', $fields ) ) {
 339              $data['title'] = array();
 340          }
 341          if ( rest_is_field_included( 'title.raw', $fields ) ) {
 342              $data['title']['raw'] = $post->post_title;
 343          }
 344          if ( rest_is_field_included( 'title.rendered', $fields ) ) {
 345              add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
 346  
 347              $data['title']['rendered'] = get_the_title( $post->ID );
 348  
 349              remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
 350          }
 351  
 352          if ( rest_is_field_included( 'settings', $fields ) ) {
 353              $data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
 354          }
 355  
 356          if ( rest_is_field_included( 'styles', $fields ) ) {
 357              $data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
 358          }
 359  
 360          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 361          $data    = $this->add_additional_fields_to_object( $data, $request );
 362          $data    = $this->filter_response_by_context( $data, $context );
 363  
 364          // Wrap the data in a response object.
 365          $response = rest_ensure_response( $data );
 366  
 367          $links = $this->prepare_links( $post->ID );
 368          $response->add_links( $links );
 369          if ( ! empty( $links['self']['href'] ) ) {
 370              $actions = $this->get_available_actions();
 371              $self    = $links['self']['href'];
 372              foreach ( $actions as $rel ) {
 373                  $response->add_link( $rel, $self );
 374              }
 375          }
 376  
 377          return $response;
 378      }
 379  
 380      /**
 381       * Get the post, if the ID is valid.
 382       *
 383       * @since 5.9.0
 384       *
 385       * @param int $id Supplied ID.
 386       * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
 387       */
 388  	protected function get_post( $id ) {
 389          $error = new WP_Error(
 390              'rest_global_styles_not_found',
 391              __( 'No global styles config exist with that id.' ),
 392              array( 'status' => 404 )
 393          );
 394  
 395          $id = (int) $id;
 396          if ( $id <= 0 ) {
 397              return $error;
 398          }
 399  
 400          $post = get_post( $id );
 401          if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
 402              return $error;
 403          }
 404  
 405          return $post;
 406      }
 407  
 408  
 409      /**
 410       * Prepares links for the request.
 411       *
 412       * @since 5.9.0
 413       *
 414       * @param integer $id ID.
 415       * @return array Links for the given post.
 416       */
 417  	protected function prepare_links( $id ) {
 418          $base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
 419  
 420          $links = array(
 421              'self' => array(
 422                  'href' => rest_url( trailingslashit( $base ) . $id ),
 423              ),
 424          );
 425  
 426          return $links;
 427      }
 428  
 429      /**
 430       * Get the link relations available for the post and current user.
 431       *
 432       * @since 5.9.0
 433       *
 434       * @return array List of link relations.
 435       */
 436  	protected function get_available_actions() {
 437          $rels = array();
 438  
 439          $post_type = get_post_type_object( $this->post_type );
 440          if ( current_user_can( $post_type->cap->publish_posts ) ) {
 441              $rels[] = 'https://api.w.org/action-publish';
 442          }
 443  
 444          return $rels;
 445      }
 446  
 447      /**
 448       * Overwrites the default protected title format.
 449       *
 450       * By default, WordPress will show password protected posts with a title of
 451       * "Protected: %s", as the REST API communicates the protected status of a post
 452       * in a machine readable format, we remove the "Protected: " prefix.
 453       *
 454       * @since 5.9.0
 455       *
 456       * @return string Protected title format.
 457       */
 458  	public function protected_title_format() {
 459          return '%s';
 460      }
 461  
 462      /**
 463       * Retrieves the query params for the global styles collection.
 464       *
 465       * @since 5.9.0
 466       *
 467       * @return array Collection parameters.
 468       */
 469  	public function get_collection_params() {
 470          return array();
 471      }
 472  
 473      /**
 474       * Retrieves the global styles type' schema, conforming to JSON Schema.
 475       *
 476       * @since 5.9.0
 477       *
 478       * @return array Item schema data.
 479       */
 480  	public function get_item_schema() {
 481          if ( $this->schema ) {
 482              return $this->add_additional_fields_schema( $this->schema );
 483          }
 484  
 485          $schema = array(
 486              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 487              'title'      => $this->post_type,
 488              'type'       => 'object',
 489              'properties' => array(
 490                  'id'       => array(
 491                      'description' => __( 'ID of global styles config.' ),
 492                      'type'        => 'string',
 493                      'context'     => array( 'embed', 'view', 'edit' ),
 494                      'readonly'    => true,
 495                  ),
 496                  'styles'   => array(
 497                      'description' => __( 'Global styles.' ),
 498                      'type'        => array( 'object' ),
 499                      'context'     => array( 'view', 'edit' ),
 500                  ),
 501                  'settings' => array(
 502                      'description' => __( 'Global settings.' ),
 503                      'type'        => array( 'object' ),
 504                      'context'     => array( 'view', 'edit' ),
 505                  ),
 506                  'title'    => array(
 507                      'description' => __( 'Title of the global styles variation.' ),
 508                      'type'        => array( 'object', 'string' ),
 509                      'default'     => '',
 510                      'context'     => array( 'embed', 'view', 'edit' ),
 511                      'properties'  => array(
 512                          'raw'      => array(
 513                              'description' => __( 'Title for the global styles variation, as it exists in the database.' ),
 514                              'type'        => 'string',
 515                              'context'     => array( 'view', 'edit', 'embed' ),
 516                          ),
 517                          'rendered' => array(
 518                              'description' => __( 'HTML title for the post, transformed for display.' ),
 519                              'type'        => 'string',
 520                              'context'     => array( 'view', 'edit', 'embed' ),
 521                              'readonly'    => true,
 522                          ),
 523                      ),
 524                  ),
 525              ),
 526          );
 527  
 528          $this->schema = $schema;
 529  
 530          return $this->add_additional_fields_schema( $this->schema );
 531      }
 532  
 533      /**
 534       * Checks if a given request has access to read a single theme global styles config.
 535       *
 536       * @since 5.9.0
 537       *
 538       * @param WP_REST_Request $request Full details about the request.
 539       * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
 540       */
 541  	public function get_theme_item_permissions_check( $request ) {
 542          // Verify if the current user has edit_theme_options capability.
 543          // This capability is required to edit/view/delete templates.
 544          if ( ! current_user_can( 'edit_theme_options' ) ) {
 545              return new WP_Error(
 546                  'rest_cannot_manage_global_styles',
 547                  __( 'Sorry, you are not allowed to access the global styles on this site.' ),
 548                  array(
 549                      'status' => rest_authorization_required_code(),
 550                  )
 551              );
 552          }
 553  
 554          return true;
 555      }
 556  
 557      /**
 558       * Returns the given theme global styles config.
 559       *
 560       * @since 5.9.0
 561       *
 562       * @param WP_REST_Request $request The request instance.
 563       * @return WP_REST_Response|WP_Error
 564       */
 565  	public function get_theme_item( $request ) {
 566          if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) {
 567              // This endpoint only supports the active theme for now.
 568              return new WP_Error(
 569                  'rest_theme_not_found',
 570                  __( 'Theme not found.' ),
 571                  array( 'status' => 404 )
 572              );
 573          }
 574  
 575          $theme  = WP_Theme_JSON_Resolver::get_merged_data( 'theme' );
 576          $data   = array();
 577          $fields = $this->get_fields_for_response( $request );
 578  
 579          if ( rest_is_field_included( 'settings', $fields ) ) {
 580              $data['settings'] = $theme->get_settings();
 581          }
 582  
 583          if ( rest_is_field_included( 'styles', $fields ) ) {
 584              $raw_data       = $theme->get_raw_data();
 585              $data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array();
 586          }
 587  
 588          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 589          $data    = $this->add_additional_fields_to_object( $data, $request );
 590          $data    = $this->filter_response_by_context( $data, $context );
 591  
 592          $response = rest_ensure_response( $data );
 593  
 594          $links = array(
 595              'self' => array(
 596                  'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ),
 597              ),
 598          );
 599  
 600          $response->add_links( $links );
 601  
 602          return $response;
 603      }
 604  
 605      /**
 606       * Checks if a given request has access to read a single theme global styles config.
 607       *
 608       * @since 6.0.0
 609       *
 610       * @param WP_REST_Request $request Full details about the request.
 611       * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
 612       */
 613  	public function get_theme_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
 614          // Verify if the current user has edit_theme_options capability.
 615          // This capability is required to edit/view/delete templates.
 616          if ( ! current_user_can( 'edit_theme_options' ) ) {
 617              return new WP_Error(
 618                  'rest_cannot_manage_global_styles',
 619                  __( 'Sorry, you are not allowed to access the global styles on this site.' ),
 620                  array(
 621                      'status' => rest_authorization_required_code(),
 622                  )
 623              );
 624          }
 625  
 626          return true;
 627      }
 628  
 629      /**
 630       * Returns the given theme global styles variations.
 631       *
 632       * @since 6.0.0
 633       *
 634       * @param WP_REST_Request $request The request instance.
 635       *
 636       * @return WP_REST_Response|WP_Error
 637       */
 638  	public function get_theme_items( $request ) {
 639          if ( wp_get_theme()->get_stylesheet() !== $request['stylesheet'] ) {
 640              // This endpoint only supports the active theme for now.
 641              return new WP_Error(
 642                  'rest_theme_not_found',
 643                  __( 'Theme not found.' ),
 644                  array( 'status' => 404 )
 645              );
 646          }
 647  
 648          $variations = WP_Theme_JSON_Resolver::get_style_variations();
 649          $response   = rest_ensure_response( $variations );
 650  
 651          return $response;
 652      }
 653  }


Generated: Tue Dec 3 01:00:02 2024 Cross-referenced by PHPXref 0.7.1