wp/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-revisions-controller.php
changeset 21 48c4eec2b7e6
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 <?php
       
     2 /**
       
     3  * REST API: WP_REST_Global_Styles_Revisions_Controller class
       
     4  *
       
     5  * @package WordPress
       
     6  * @subpackage REST_API
       
     7  * @since 6.3.0
       
     8  */
       
     9 
       
    10 /**
       
    11  * Core class used to access global styles revisions via the REST API.
       
    12  *
       
    13  * @since 6.3.0
       
    14  *
       
    15  * @see WP_REST_Controller
       
    16  */
       
    17 class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Revisions_Controller {
       
    18 	/**
       
    19 	 * Parent controller.
       
    20 	 *
       
    21 	 * @since 6.6.0
       
    22 	 * @var WP_REST_Controller
       
    23 	 */
       
    24 	private $parent_controller;
       
    25 
       
    26 	/**
       
    27 	 * The base of the parent controller's route.
       
    28 	 *
       
    29 	 * @since 6.3.0
       
    30 	 * @var string
       
    31 	 */
       
    32 	protected $parent_base;
       
    33 
       
    34 	/**
       
    35 	 * Parent post type.
       
    36 	 *
       
    37 	 * @since 6.6.0
       
    38 	 * @var string
       
    39 	 */
       
    40 	protected $parent_post_type;
       
    41 
       
    42 	/**
       
    43 	 * Constructor.
       
    44 	 *
       
    45 	 * @since 6.3.0
       
    46 	 * @since 6.6.0 Extends class from WP_REST_Revisions_Controller.
       
    47 	 *
       
    48 	 * @param string $parent_post_type Post type of the parent.
       
    49 	 */
       
    50 	public function __construct( $parent_post_type = 'wp_global_styles' ) {
       
    51 		parent::__construct( $parent_post_type );
       
    52 		$post_type_object  = get_post_type_object( $parent_post_type );
       
    53 		$parent_controller = $post_type_object->get_rest_controller();
       
    54 
       
    55 		if ( ! $parent_controller ) {
       
    56 			$parent_controller = new WP_REST_Global_Styles_Controller( $parent_post_type );
       
    57 		}
       
    58 
       
    59 		$this->parent_controller = $parent_controller;
       
    60 		$this->rest_base         = 'revisions';
       
    61 		$this->parent_base       = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
       
    62 		$this->namespace         = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
       
    63 	}
       
    64 
       
    65 	/**
       
    66 	 * Registers the controller's routes.
       
    67 	 *
       
    68 	 * @since 6.3.0
       
    69 	 * @since 6.6.0 Added route to fetch individual global styles revisions.
       
    70 	 */
       
    71 	public function register_routes() {
       
    72 		register_rest_route(
       
    73 			$this->namespace,
       
    74 			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
       
    75 			array(
       
    76 				'args'   => array(
       
    77 					'parent' => array(
       
    78 						'description' => __( 'The ID for the parent of the revision.' ),
       
    79 						'type'        => 'integer',
       
    80 					),
       
    81 				),
       
    82 				array(
       
    83 					'methods'             => WP_REST_Server::READABLE,
       
    84 					'callback'            => array( $this, 'get_items' ),
       
    85 					'permission_callback' => array( $this, 'get_items_permissions_check' ),
       
    86 					'args'                => $this->get_collection_params(),
       
    87 				),
       
    88 				'schema' => array( $this, 'get_public_item_schema' ),
       
    89 			)
       
    90 		);
       
    91 
       
    92 		register_rest_route(
       
    93 			$this->namespace,
       
    94 			'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
       
    95 			array(
       
    96 				'args'   => array(
       
    97 					'parent' => array(
       
    98 						'description' => __( 'The ID for the parent of the global styles revision.' ),
       
    99 						'type'        => 'integer',
       
   100 					),
       
   101 					'id'     => array(
       
   102 						'description' => __( 'Unique identifier for the global styles revision.' ),
       
   103 						'type'        => 'integer',
       
   104 					),
       
   105 				),
       
   106 				array(
       
   107 					'methods'             => WP_REST_Server::READABLE,
       
   108 					'callback'            => array( $this, 'get_item' ),
       
   109 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
       
   110 					'args'                => array(
       
   111 						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
       
   112 					),
       
   113 				),
       
   114 				'schema' => array( $this, 'get_public_item_schema' ),
       
   115 			)
       
   116 		);
       
   117 	}
       
   118 
       
   119 	/**
       
   120 	 * Returns decoded JSON from post content string,
       
   121 	 * or a 404 if not found.
       
   122 	 *
       
   123 	 * @since 6.3.0
       
   124 	 *
       
   125 	 * @param string $raw_json Encoded JSON from global styles custom post content.
       
   126 	 * @return Array|WP_Error
       
   127 	 */
       
   128 	protected function get_decoded_global_styles_json( $raw_json ) {
       
   129 		$decoded_json = json_decode( $raw_json, true );
       
   130 
       
   131 		if ( is_array( $decoded_json ) && isset( $decoded_json['isGlobalStylesUserThemeJSON'] ) && true === $decoded_json['isGlobalStylesUserThemeJSON'] ) {
       
   132 			return $decoded_json;
       
   133 		}
       
   134 
       
   135 		return new WP_Error(
       
   136 			'rest_global_styles_not_found',
       
   137 			__( 'Cannot find user global styles revisions.' ),
       
   138 			array( 'status' => 404 )
       
   139 		);
       
   140 	}
       
   141 
       
   142 	/**
       
   143 	 * Returns paginated revisions of the given global styles config custom post type.
       
   144 	 *
       
   145 	 * The bulk of the body is taken from WP_REST_Revisions_Controller->get_items,
       
   146 	 * but global styles does not require as many parameters.
       
   147 	 *
       
   148 	 * @since 6.3.0
       
   149 	 *
       
   150 	 * @param WP_REST_Request $request The request instance.
       
   151 	 * @return WP_REST_Response|WP_Error
       
   152 	 */
       
   153 	public function get_items( $request ) {
       
   154 		$parent = $this->get_parent( $request['parent'] );
       
   155 
       
   156 		if ( is_wp_error( $parent ) ) {
       
   157 			return $parent;
       
   158 		}
       
   159 
       
   160 		$global_styles_config = $this->get_decoded_global_styles_json( $parent->post_content );
       
   161 
       
   162 		if ( is_wp_error( $global_styles_config ) ) {
       
   163 			return $global_styles_config;
       
   164 		}
       
   165 
       
   166 		if ( wp_revisions_enabled( $parent ) ) {
       
   167 			$registered = $this->get_collection_params();
       
   168 			$query_args = array(
       
   169 				'post_parent'    => $parent->ID,
       
   170 				'post_type'      => 'revision',
       
   171 				'post_status'    => 'inherit',
       
   172 				'posts_per_page' => -1,
       
   173 				'orderby'        => 'date ID',
       
   174 				'order'          => 'DESC',
       
   175 			);
       
   176 
       
   177 			$parameter_mappings = array(
       
   178 				'offset'   => 'offset',
       
   179 				'page'     => 'paged',
       
   180 				'per_page' => 'posts_per_page',
       
   181 			);
       
   182 
       
   183 			foreach ( $parameter_mappings as $api_param => $wp_param ) {
       
   184 				if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
       
   185 					$query_args[ $wp_param ] = $request[ $api_param ];
       
   186 				}
       
   187 			}
       
   188 
       
   189 			$revisions_query = new WP_Query();
       
   190 			$revisions       = $revisions_query->query( $query_args );
       
   191 			$offset          = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
       
   192 			$page            = (int) $query_args['paged'];
       
   193 			$total_revisions = $revisions_query->found_posts;
       
   194 
       
   195 			if ( $total_revisions < 1 ) {
       
   196 				// Out-of-bounds, run the query again without LIMIT for total count.
       
   197 				unset( $query_args['paged'], $query_args['offset'] );
       
   198 				$count_query = new WP_Query();
       
   199 				$count_query->query( $query_args );
       
   200 
       
   201 				$total_revisions = $count_query->found_posts;
       
   202 			}
       
   203 
       
   204 			if ( $revisions_query->query_vars['posts_per_page'] > 0 ) {
       
   205 				$max_pages = (int) ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
       
   206 			} else {
       
   207 				$max_pages = $total_revisions > 0 ? 1 : 0;
       
   208 			}
       
   209 			if ( $total_revisions > 0 ) {
       
   210 				if ( $offset >= $total_revisions ) {
       
   211 					return new WP_Error(
       
   212 						'rest_revision_invalid_offset_number',
       
   213 						__( 'The offset number requested is larger than or equal to the number of available revisions.' ),
       
   214 						array( 'status' => 400 )
       
   215 					);
       
   216 				} elseif ( ! $offset && $page > $max_pages ) {
       
   217 					return new WP_Error(
       
   218 						'rest_revision_invalid_page_number',
       
   219 						__( 'The page number requested is larger than the number of pages available.' ),
       
   220 						array( 'status' => 400 )
       
   221 					);
       
   222 				}
       
   223 			}
       
   224 		} else {
       
   225 			$revisions       = array();
       
   226 			$total_revisions = 0;
       
   227 			$max_pages       = 0;
       
   228 			$page            = (int) $request['page'];
       
   229 		}
       
   230 
       
   231 		$response = array();
       
   232 
       
   233 		foreach ( $revisions as $revision ) {
       
   234 			$data       = $this->prepare_item_for_response( $revision, $request );
       
   235 			$response[] = $this->prepare_response_for_collection( $data );
       
   236 		}
       
   237 
       
   238 		$response = rest_ensure_response( $response );
       
   239 
       
   240 		$response->header( 'X-WP-Total', (int) $total_revisions );
       
   241 		$response->header( 'X-WP-TotalPages', (int) $max_pages );
       
   242 
       
   243 		$request_params = $request->get_query_params();
       
   244 		$base_path      = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
       
   245 		$base           = add_query_arg( urlencode_deep( $request_params ), $base_path );
       
   246 
       
   247 		if ( $page > 1 ) {
       
   248 			$prev_page = $page - 1;
       
   249 
       
   250 			if ( $prev_page > $max_pages ) {
       
   251 				$prev_page = $max_pages;
       
   252 			}
       
   253 
       
   254 			$prev_link = add_query_arg( 'page', $prev_page, $base );
       
   255 			$response->link_header( 'prev', $prev_link );
       
   256 		}
       
   257 		if ( $max_pages > $page ) {
       
   258 			$next_page = $page + 1;
       
   259 			$next_link = add_query_arg( 'page', $next_page, $base );
       
   260 
       
   261 			$response->link_header( 'next', $next_link );
       
   262 		}
       
   263 
       
   264 		return $response;
       
   265 	}
       
   266 
       
   267 	/**
       
   268 	 * Prepares the revision for the REST response.
       
   269 	 *
       
   270 	 * @since 6.3.0
       
   271 	 * @since 6.6.0 Added resolved URI links to the response.
       
   272 	 *
       
   273 	 * @param WP_Post         $post    Post revision object.
       
   274 	 * @param WP_REST_Request $request Request object.
       
   275 	 * @return WP_REST_Response|WP_Error Response object.
       
   276 	 */
       
   277 	public function prepare_item_for_response( $post, $request ) {
       
   278 		$parent               = $this->get_parent( $request['parent'] );
       
   279 		$global_styles_config = $this->get_decoded_global_styles_json( $post->post_content );
       
   280 
       
   281 		if ( is_wp_error( $global_styles_config ) ) {
       
   282 			return $global_styles_config;
       
   283 		}
       
   284 
       
   285 		$fields     = $this->get_fields_for_response( $request );
       
   286 		$data       = array();
       
   287 		$theme_json = null;
       
   288 
       
   289 		if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) {
       
   290 			$theme_json           = new WP_Theme_JSON( $global_styles_config, 'custom' );
       
   291 			$global_styles_config = $theme_json->get_raw_data();
       
   292 			if ( rest_is_field_included( 'settings', $fields ) ) {
       
   293 				$data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass();
       
   294 			}
       
   295 			if ( rest_is_field_included( 'styles', $fields ) ) {
       
   296 				$data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass();
       
   297 			}
       
   298 		}
       
   299 
       
   300 		if ( rest_is_field_included( 'author', $fields ) ) {
       
   301 			$data['author'] = (int) $post->post_author;
       
   302 		}
       
   303 
       
   304 		if ( rest_is_field_included( 'date', $fields ) ) {
       
   305 			$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
       
   306 		}
       
   307 
       
   308 		if ( rest_is_field_included( 'date_gmt', $fields ) ) {
       
   309 			$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
       
   310 		}
       
   311 
       
   312 		if ( rest_is_field_included( 'id', $fields ) ) {
       
   313 			$data['id'] = (int) $post->ID;
       
   314 		}
       
   315 
       
   316 		if ( rest_is_field_included( 'modified', $fields ) ) {
       
   317 			$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
       
   318 		}
       
   319 
       
   320 		if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
       
   321 			$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
       
   322 		}
       
   323 
       
   324 		if ( rest_is_field_included( 'parent', $fields ) ) {
       
   325 			$data['parent'] = (int) $parent->ID;
       
   326 		}
       
   327 
       
   328 		$context             = ! empty( $request['context'] ) ? $request['context'] : 'view';
       
   329 		$data                = $this->add_additional_fields_to_object( $data, $request );
       
   330 		$data                = $this->filter_response_by_context( $data, $context );
       
   331 		$response            = rest_ensure_response( $data );
       
   332 		$resolved_theme_uris = WP_Theme_JSON_Resolver::get_resolved_theme_uris( $theme_json );
       
   333 
       
   334 		if ( ! empty( $resolved_theme_uris ) ) {
       
   335 			$response->add_links(
       
   336 				array(
       
   337 					'https://api.w.org/theme-file' => $resolved_theme_uris,
       
   338 				)
       
   339 			);
       
   340 		}
       
   341 
       
   342 		return $response;
       
   343 	}
       
   344 
       
   345 	/**
       
   346 	 * Retrieves the revision's schema, conforming to JSON Schema.
       
   347 	 *
       
   348 	 * @since 6.3.0
       
   349 	 * @since 6.6.0 Merged parent and parent controller schema data.
       
   350 	 *
       
   351 	 * @return array Item schema data.
       
   352 	 */
       
   353 	public function get_item_schema() {
       
   354 		if ( $this->schema ) {
       
   355 			return $this->add_additional_fields_schema( $this->schema );
       
   356 		}
       
   357 
       
   358 		$schema               = parent::get_item_schema();
       
   359 		$parent_schema        = $this->parent_controller->get_item_schema();
       
   360 		$schema['properties'] = array_merge( $schema['properties'], $parent_schema['properties'] );
       
   361 
       
   362 		unset(
       
   363 			$schema['properties']['guid'],
       
   364 			$schema['properties']['slug'],
       
   365 			$schema['properties']['meta'],
       
   366 			$schema['properties']['content'],
       
   367 			$schema['properties']['title']
       
   368 		);
       
   369 
       
   370 			$this->schema = $schema;
       
   371 
       
   372 		return $this->add_additional_fields_schema( $this->schema );
       
   373 	}
       
   374 
       
   375 	/**
       
   376 	 * Retrieves the query params for collections.
       
   377 	 * Removes params that are not supported by global styles revisions.
       
   378 	 *
       
   379 	 * @since 6.6.0
       
   380 	 *
       
   381 	 * @return array Collection parameters.
       
   382 	 */
       
   383 	public function get_collection_params() {
       
   384 		$query_params = parent::get_collection_params();
       
   385 		unset(
       
   386 			$query_params['exclude'],
       
   387 			$query_params['include'],
       
   388 			$query_params['search'],
       
   389 			$query_params['order'],
       
   390 			$query_params['orderby']
       
   391 		);
       
   392 		return $query_params;
       
   393 	}
       
   394 }