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


Generated: Tue Jan 18 01:00:03 2022 Cross-referenced by PHPXref 0.7.1