ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-wp-rest-themes-controller.php (18127B)


      1 <?php
      2 /**
      3  * REST API: WP_REST_Themes_Controller class
      4  *
      5  * @package WordPress
      6  * @subpackage REST_API
      7  * @since 5.0.0
      8  */
      9 
     10 /**
     11  * Core class used to manage themes via the REST API.
     12  *
     13  * @since 5.0.0
     14  *
     15  * @see WP_REST_Controller
     16  */
     17 class WP_REST_Themes_Controller extends WP_REST_Controller {
     18 
     19 	/**
     20 	 * Constructor.
     21 	 *
     22 	 * @since 5.0.0
     23 	 */
     24 	public function __construct() {
     25 		$this->namespace = 'wp/v2';
     26 		$this->rest_base = 'themes';
     27 	}
     28 
     29 	/**
     30 	 * Registers the routes for themes.
     31 	 *
     32 	 * @since 5.0.0
     33 	 *
     34 	 * @see register_rest_route()
     35 	 */
     36 	public function register_routes() {
     37 		register_rest_route(
     38 			$this->namespace,
     39 			'/' . $this->rest_base,
     40 			array(
     41 				array(
     42 					'methods'             => WP_REST_Server::READABLE,
     43 					'callback'            => array( $this, 'get_items' ),
     44 					'permission_callback' => array( $this, 'get_items_permissions_check' ),
     45 					'args'                => $this->get_collection_params(),
     46 				),
     47 				'schema' => array( $this, 'get_item_schema' ),
     48 			)
     49 		);
     50 
     51 		register_rest_route(
     52 			$this->namespace,
     53 			'/' . $this->rest_base . '/(?P<stylesheet>[\w-]+)',
     54 			array(
     55 				'args'   => array(
     56 					'stylesheet' => array(
     57 						'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
     58 						'type'        => 'string',
     59 					),
     60 				),
     61 				array(
     62 					'methods'             => WP_REST_Server::READABLE,
     63 					'callback'            => array( $this, 'get_item' ),
     64 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
     65 				),
     66 				'schema' => array( $this, 'get_public_item_schema' ),
     67 			)
     68 		);
     69 	}
     70 
     71 	/**
     72 	 * Checks if a given request has access to read the theme.
     73 	 *
     74 	 * @since 5.0.0
     75 	 *
     76 	 * @param WP_REST_Request $request Full details about the request.
     77 	 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
     78 	 */
     79 	public function get_items_permissions_check( $request ) {
     80 		if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
     81 			return true;
     82 		}
     83 
     84 		$registered = $this->get_collection_params();
     85 		if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) {
     86 			return $this->check_read_active_theme_permission();
     87 		}
     88 
     89 		return new WP_Error(
     90 			'rest_cannot_view_themes',
     91 			__( 'Sorry, you are not allowed to view themes.' ),
     92 			array( 'status' => rest_authorization_required_code() )
     93 		);
     94 	}
     95 
     96 	/**
     97 	 * Checks if a given request has access to read the theme.
     98 	 *
     99 	 * @since 5.7.0
    100 	 *
    101 	 * @param WP_REST_Request $request Full details about the request.
    102 	 * @return bool|WP_Error True if the request has read access for the item, otherwise WP_Error object.
    103 	 */
    104 	public function get_item_permissions_check( $request ) {
    105 		if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
    106 			return true;
    107 		}
    108 
    109 		$wp_theme      = wp_get_theme( $request['stylesheet'] );
    110 		$current_theme = wp_get_theme();
    111 
    112 		if ( $this->is_same_theme( $wp_theme, $current_theme ) ) {
    113 			return $this->check_read_active_theme_permission();
    114 		}
    115 
    116 		return new WP_Error(
    117 			'rest_cannot_view_themes',
    118 			__( 'Sorry, you are not allowed to view themes.' ),
    119 			array( 'status' => rest_authorization_required_code() )
    120 		);
    121 	}
    122 
    123 	/**
    124 	 * Checks if a theme can be read.
    125 	 *
    126 	 * @since 5.7.0
    127 	 *
    128 	 * @return bool|WP_Error Whether the theme can be read.
    129 	 */
    130 	protected function check_read_active_theme_permission() {
    131 		if ( current_user_can( 'edit_posts' ) ) {
    132 			return true;
    133 		}
    134 
    135 		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
    136 			if ( current_user_can( $post_type->cap->edit_posts ) ) {
    137 				return true;
    138 			}
    139 		}
    140 
    141 		return new WP_Error(
    142 			'rest_cannot_view_active_theme',
    143 			__( 'Sorry, you are not allowed to view the active theme.' ),
    144 			array( 'status' => rest_authorization_required_code() )
    145 		);
    146 	}
    147 
    148 	/**
    149 	 * Retrieves a single theme.
    150 	 *
    151 	 * @since 5.7.0
    152 	 *
    153 	 * @param WP_REST_Request $request Full details about the request.
    154 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    155 	 */
    156 	public function get_item( $request ) {
    157 		$wp_theme = wp_get_theme( $request['stylesheet'] );
    158 		if ( ! $wp_theme->exists() ) {
    159 			return new WP_Error(
    160 				'rest_theme_not_found',
    161 				__( 'Theme not found.' ),
    162 				array( 'status' => 404 )
    163 			);
    164 		}
    165 		$data = $this->prepare_item_for_response( $wp_theme, $request );
    166 
    167 		return rest_ensure_response( $data );
    168 	}
    169 
    170 	/**
    171 	 * Retrieves a collection of themes.
    172 	 *
    173 	 * @since 5.0.0
    174 	 *
    175 	 * @param WP_REST_Request $request Full details about the request.
    176 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    177 	 */
    178 	public function get_items( $request ) {
    179 		$themes = array();
    180 
    181 		$active_themes = wp_get_themes();
    182 		$current_theme = wp_get_theme();
    183 		$status        = $request['status'];
    184 
    185 		foreach ( $active_themes as $theme_name => $theme ) {
    186 			$theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
    187 			if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) {
    188 				continue;
    189 			}
    190 
    191 			$prepared = $this->prepare_item_for_response( $theme, $request );
    192 			$themes[] = $this->prepare_response_for_collection( $prepared );
    193 		}
    194 
    195 		$response = rest_ensure_response( $themes );
    196 
    197 		$response->header( 'X-WP-Total', count( $themes ) );
    198 		$response->header( 'X-WP-TotalPages', 1 );
    199 
    200 		return $response;
    201 	}
    202 
    203 	/**
    204 	 * Prepares a single theme output for response.
    205 	 *
    206 	 * @since 5.0.0
    207 	 *
    208 	 * @param WP_Theme        $theme   Theme object.
    209 	 * @param WP_REST_Request $request Request object.
    210 	 * @return WP_REST_Response Response object.
    211 	 */
    212 	public function prepare_item_for_response( $theme, $request ) {
    213 		$data   = array();
    214 		$fields = $this->get_fields_for_response( $request );
    215 
    216 		if ( rest_is_field_included( 'stylesheet', $fields ) ) {
    217 			$data['stylesheet'] = $theme->get_stylesheet();
    218 		}
    219 
    220 		if ( rest_is_field_included( 'template', $fields ) ) {
    221 			/**
    222 			 * Use the get_template() method, not the 'Template' header, for finding the template.
    223 			 * The 'Template' header is only good for what was written in the style.css, while
    224 			 * get_template() takes into account where WordPress actually located the theme and
    225 			 * whether it is actually valid.
    226 			 */
    227 			$data['template'] = $theme->get_template();
    228 		}
    229 
    230 		$plain_field_mappings = array(
    231 			'requires_php' => 'RequiresPHP',
    232 			'requires_wp'  => 'RequiresWP',
    233 			'textdomain'   => 'TextDomain',
    234 			'version'      => 'Version',
    235 		);
    236 
    237 		foreach ( $plain_field_mappings as $field => $header ) {
    238 			if ( rest_is_field_included( $field, $fields ) ) {
    239 				$data[ $field ] = $theme->get( $header );
    240 			}
    241 		}
    242 
    243 		if ( rest_is_field_included( 'screenshot', $fields ) ) {
    244 			// Using $theme->get_screenshot() with no args to get absolute URL.
    245 			$data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : '';
    246 		}
    247 
    248 		$rich_field_mappings = array(
    249 			'author'      => 'Author',
    250 			'author_uri'  => 'AuthorURI',
    251 			'description' => 'Description',
    252 			'name'        => 'Name',
    253 			'tags'        => 'Tags',
    254 			'theme_uri'   => 'ThemeURI',
    255 		);
    256 
    257 		foreach ( $rich_field_mappings as $field => $header ) {
    258 			if ( rest_is_field_included( "{$field}.raw", $fields ) ) {
    259 				$data[ $field ]['raw'] = $theme->display( $header, false, true );
    260 			}
    261 
    262 			if ( rest_is_field_included( "{$field}.rendered", $fields ) ) {
    263 				$data[ $field ]['rendered'] = $theme->display( $header );
    264 			}
    265 		}
    266 
    267 		$current_theme = wp_get_theme();
    268 		if ( rest_is_field_included( 'status', $fields ) ) {
    269 			$data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
    270 		}
    271 
    272 		if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) {
    273 			foreach ( get_registered_theme_features() as $feature => $config ) {
    274 				if ( ! is_array( $config['show_in_rest'] ) ) {
    275 					continue;
    276 				}
    277 
    278 				$name = $config['show_in_rest']['name'];
    279 
    280 				if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) {
    281 					continue;
    282 				}
    283 
    284 				if ( ! current_theme_supports( $feature ) ) {
    285 					$data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default'];
    286 					continue;
    287 				}
    288 
    289 				$support = get_theme_support( $feature );
    290 
    291 				if ( isset( $config['show_in_rest']['prepare_callback'] ) ) {
    292 					$prepare = $config['show_in_rest']['prepare_callback'];
    293 				} else {
    294 					$prepare = array( $this, 'prepare_theme_support' );
    295 				}
    296 
    297 				$prepared = $prepare( $support, $config, $feature, $request );
    298 
    299 				if ( is_wp_error( $prepared ) ) {
    300 					continue;
    301 				}
    302 
    303 				$data['theme_supports'][ $name ] = $prepared;
    304 			}
    305 		}
    306 
    307 		$data = $this->add_additional_fields_to_object( $data, $request );
    308 
    309 		// Wrap the data in a response object.
    310 		$response = rest_ensure_response( $data );
    311 
    312 		$response->add_links( $this->prepare_links( $theme ) );
    313 
    314 		/**
    315 		 * Filters theme data returned from the REST API.
    316 		 *
    317 		 * @since 5.0.0
    318 		 *
    319 		 * @param WP_REST_Response $response The response object.
    320 		 * @param WP_Theme         $theme    Theme object used to create response.
    321 		 * @param WP_REST_Request  $request  Request object.
    322 		 */
    323 		return apply_filters( 'rest_prepare_theme', $response, $theme, $request );
    324 	}
    325 
    326 	/**
    327 	 * Prepares links for the request.
    328 	 *
    329 	 * @since 5.7.0
    330 	 *
    331 	 * @param WP_Theme $theme Theme data.
    332 	 * @return array Links for the given block type.
    333 	 */
    334 	protected function prepare_links( $theme ) {
    335 		return array(
    336 			'self'       => array(
    337 				'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ),
    338 			),
    339 			'collection' => array(
    340 				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
    341 			),
    342 		);
    343 	}
    344 
    345 	/**
    346 	 * Helper function to compare two themes.
    347 	 *
    348 	 * @since 5.7.0
    349 	 *
    350 	 * @param WP_Theme $theme_a First theme to compare.
    351 	 * @param WP_Theme $theme_b Second theme to compare.
    352 	 * @return bool
    353 	 */
    354 	protected function is_same_theme( $theme_a, $theme_b ) {
    355 		return $theme_a->get_stylesheet() === $theme_b->get_stylesheet();
    356 	}
    357 
    358 	/**
    359 	 * Prepares the theme support value for inclusion in the REST API response.
    360 	 *
    361 	 * @since 5.5.0
    362 	 *
    363 	 * @param mixed           $support The raw value from get_theme_support().
    364 	 * @param array           $args    The feature's registration args.
    365 	 * @param string          $feature The feature name.
    366 	 * @param WP_REST_Request $request The request object.
    367 	 * @return mixed The prepared support value.
    368 	 */
    369 	protected function prepare_theme_support( $support, $args, $feature, $request ) {
    370 		$schema = $args['show_in_rest']['schema'];
    371 
    372 		if ( 'boolean' === $schema['type'] ) {
    373 			return true;
    374 		}
    375 
    376 		if ( is_array( $support ) && ! $args['variadic'] ) {
    377 			$support = $support[0];
    378 		}
    379 
    380 		return rest_sanitize_value_from_schema( $support, $schema );
    381 	}
    382 
    383 	/**
    384 	 * Retrieves the theme's schema, conforming to JSON Schema.
    385 	 *
    386 	 * @since 5.0.0
    387 	 *
    388 	 * @return array Item schema data.
    389 	 */
    390 	public function get_item_schema() {
    391 		if ( $this->schema ) {
    392 			return $this->add_additional_fields_schema( $this->schema );
    393 		}
    394 
    395 		$schema = array(
    396 			'$schema'    => 'http://json-schema.org/draft-04/schema#',
    397 			'title'      => 'theme',
    398 			'type'       => 'object',
    399 			'properties' => array(
    400 				'stylesheet'     => array(
    401 					'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ),
    402 					'type'        => 'string',
    403 					'readonly'    => true,
    404 				),
    405 				'template'       => array(
    406 					'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ),
    407 					'type'        => 'string',
    408 					'readonly'    => true,
    409 				),
    410 				'author'         => array(
    411 					'description' => __( 'The theme author.' ),
    412 					'type'        => 'object',
    413 					'readonly'    => true,
    414 					'properties'  => array(
    415 						'raw'      => array(
    416 							'description' => __( 'The theme author\'s name, as found in the theme header.' ),
    417 							'type'        => 'string',
    418 						),
    419 						'rendered' => array(
    420 							'description' => __( 'HTML for the theme author, transformed for display.' ),
    421 							'type'        => 'string',
    422 						),
    423 					),
    424 				),
    425 				'author_uri'     => array(
    426 					'description' => __( 'The website of the theme author.' ),
    427 					'type'        => 'object',
    428 					'readonly'    => true,
    429 					'properties'  => array(
    430 						'raw'      => array(
    431 							'description' => __( 'The website of the theme author, as found in the theme header.' ),
    432 							'type'        => 'string',
    433 							'format'      => 'uri',
    434 						),
    435 						'rendered' => array(
    436 							'description' => __( 'The website of the theme author, transformed for display.' ),
    437 							'type'        => 'string',
    438 							'format'      => 'uri',
    439 						),
    440 					),
    441 				),
    442 				'description'    => array(
    443 					'description' => __( 'A description of the theme.' ),
    444 					'type'        => 'object',
    445 					'readonly'    => true,
    446 					'properties'  => array(
    447 						'raw'      => array(
    448 							'description' => __( 'The theme description, as found in the theme header.' ),
    449 							'type'        => 'string',
    450 						),
    451 						'rendered' => array(
    452 							'description' => __( 'The theme description, transformed for display.' ),
    453 							'type'        => 'string',
    454 						),
    455 					),
    456 				),
    457 				'name'           => array(
    458 					'description' => __( 'The name of the theme.' ),
    459 					'type'        => 'object',
    460 					'readonly'    => true,
    461 					'properties'  => array(
    462 						'raw'      => array(
    463 							'description' => __( 'The theme name, as found in the theme header.' ),
    464 							'type'        => 'string',
    465 						),
    466 						'rendered' => array(
    467 							'description' => __( 'The theme name, transformed for display.' ),
    468 							'type'        => 'string',
    469 						),
    470 					),
    471 				),
    472 				'requires_php'   => array(
    473 					'description' => __( 'The minimum PHP version required for the theme to work.' ),
    474 					'type'        => 'string',
    475 					'readonly'    => true,
    476 				),
    477 				'requires_wp'    => array(
    478 					'description' => __( 'The minimum WordPress version required for the theme to work.' ),
    479 					'type'        => 'string',
    480 					'readonly'    => true,
    481 				),
    482 				'screenshot'     => array(
    483 					'description' => __( 'The theme\'s screenshot URL.' ),
    484 					'type'        => 'string',
    485 					'format'      => 'uri',
    486 					'readonly'    => true,
    487 				),
    488 				'tags'           => array(
    489 					'description' => __( 'Tags indicating styles and features of the theme.' ),
    490 					'type'        => 'object',
    491 					'readonly'    => true,
    492 					'properties'  => array(
    493 						'raw'      => array(
    494 							'description' => __( 'The theme tags, as found in the theme header.' ),
    495 							'type'        => 'array',
    496 							'items'       => array(
    497 								'type' => 'string',
    498 							),
    499 						),
    500 						'rendered' => array(
    501 							'description' => __( 'The theme tags, transformed for display.' ),
    502 							'type'        => 'string',
    503 						),
    504 					),
    505 				),
    506 				'textdomain'     => array(
    507 					'description' => __( 'The theme\'s text domain.' ),
    508 					'type'        => 'string',
    509 					'readonly'    => true,
    510 				),
    511 				'theme_supports' => array(
    512 					'description' => __( 'Features supported by this theme.' ),
    513 					'type'        => 'object',
    514 					'readonly'    => true,
    515 					'properties'  => array(),
    516 				),
    517 				'theme_uri'      => array(
    518 					'description' => __( 'The URI of the theme\'s webpage.' ),
    519 					'type'        => 'object',
    520 					'readonly'    => true,
    521 					'properties'  => array(
    522 						'raw'      => array(
    523 							'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ),
    524 							'type'        => 'string',
    525 							'format'      => 'uri',
    526 						),
    527 						'rendered' => array(
    528 							'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ),
    529 							'type'        => 'string',
    530 							'format'      => 'uri',
    531 						),
    532 					),
    533 				),
    534 				'version'        => array(
    535 					'description' => __( 'The theme\'s current version.' ),
    536 					'type'        => 'string',
    537 					'readonly'    => true,
    538 				),
    539 				'status'         => array(
    540 					'description' => __( 'A named status for the theme.' ),
    541 					'type'        => 'string',
    542 					'enum'        => array( 'inactive', 'active' ),
    543 				),
    544 			),
    545 		);
    546 
    547 		foreach ( get_registered_theme_features() as $feature => $config ) {
    548 			if ( ! is_array( $config['show_in_rest'] ) ) {
    549 				continue;
    550 			}
    551 
    552 			$name = $config['show_in_rest']['name'];
    553 
    554 			$schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema'];
    555 		}
    556 
    557 		$this->schema = $schema;
    558 
    559 		return $this->add_additional_fields_schema( $this->schema );
    560 	}
    561 
    562 	/**
    563 	 * Retrieves the search params for the themes collection.
    564 	 *
    565 	 * @since 5.0.0
    566 	 *
    567 	 * @return array Collection parameters.
    568 	 */
    569 	public function get_collection_params() {
    570 		$query_params = array(
    571 			'status' => array(
    572 				'description' => __( 'Limit result set to themes assigned one or more statuses.' ),
    573 				'type'        => 'array',
    574 				'items'       => array(
    575 					'enum' => array( 'active', 'inactive' ),
    576 					'type' => 'string',
    577 				),
    578 			),
    579 		);
    580 
    581 		/**
    582 		 * Filters REST API collection parameters for the themes controller.
    583 		 *
    584 		 * @since 5.0.0
    585 		 *
    586 		 * @param array $query_params JSON Schema-formatted collection parameters.
    587 		 */
    588 		return apply_filters( 'rest_themes_collection_params', $query_params );
    589 	}
    590 
    591 	/**
    592 	 * Sanitizes and validates the list of theme status.
    593 	 *
    594 	 * @since 5.0.0
    595 	 * @deprecated 5.7.0
    596 	 *
    597 	 * @param string|array    $statuses  One or more theme statuses.
    598 	 * @param WP_REST_Request $request   Full details about the request.
    599 	 * @param string          $parameter Additional parameter to pass to validation.
    600 	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
    601 	 */
    602 	public function sanitize_theme_status( $statuses, $request, $parameter ) {
    603 		_deprecated_function( __METHOD__, '5.7.0' );
    604 
    605 		$statuses = wp_parse_slug_list( $statuses );
    606 
    607 		foreach ( $statuses as $status ) {
    608 			$result = rest_validate_request_arg( $status, $request, $parameter );
    609 
    610 			if ( is_wp_error( $result ) ) {
    611 				return $result;
    612 			}
    613 		}
    614 
    615 		return $statuses;
    616 	}
    617 }