wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 19 3d72ae0968f4
--- a/wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php	Tue Dec 15 15:52:01 2020 +0100
+++ b/wp/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php	Wed Sep 21 18:19:35 2022 +0200
@@ -27,7 +27,7 @@
 	}
 
 	/**
-	 * Registers the routes for the objects of the controller.
+	 * Registers the routes for themes.
 	 *
 	 * @since 5.0.0
 	 *
@@ -47,6 +47,25 @@
 				'schema' => array( $this, 'get_item_schema' ),
 			)
 		);
+
+		register_rest_route(
+			$this->namespace,
+			'/' . $this->rest_base . '/(?P<stylesheet>[\w-]+)',
+			array(
+				'args'   => array(
+					'stylesheet' => array(
+						'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
+						'type'        => 'string',
+					),
+				),
+				array(
+					'methods'             => WP_REST_Server::READABLE,
+					'callback'            => array( $this, 'get_item' ),
+					'permission_callback' => array( $this, 'get_item_permissions_check' ),
+				),
+				'schema' => array( $this, 'get_public_item_schema' ),
+			)
+		);
 	}
 
 	/**
@@ -58,6 +77,57 @@
 	 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
 	 */
 	public function get_items_permissions_check( $request ) {
+		if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
+			return true;
+		}
+
+		$registered = $this->get_collection_params();
+		if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) {
+			return $this->check_read_active_theme_permission();
+		}
+
+		return new WP_Error(
+			'rest_cannot_view_themes',
+			__( 'Sorry, you are not allowed to view themes.' ),
+			array( 'status' => rest_authorization_required_code() )
+		);
+	}
+
+	/**
+	 * Checks if a given request has access to read the theme.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return bool|WP_Error True if the request has read access for the item, otherwise WP_Error object.
+	 */
+	public function get_item_permissions_check( $request ) {
+		if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
+			return true;
+		}
+
+		$wp_theme      = wp_get_theme( $request['stylesheet'] );
+		$current_theme = wp_get_theme();
+
+		if ( $this->is_same_theme( $wp_theme, $current_theme ) ) {
+			return $this->check_read_active_theme_permission();
+		}
+
+		return new WP_Error(
+			'rest_cannot_view_themes',
+			__( 'Sorry, you are not allowed to view themes.' ),
+			array( 'status' => rest_authorization_required_code() )
+		);
+	}
+
+	/**
+	 * Checks if a theme can be read.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @return bool|WP_Error Whether the theme can be read.
+	 */
+	protected function check_read_active_theme_permission() {
 		if ( current_user_can( 'edit_posts' ) ) {
 			return true;
 		}
@@ -69,13 +139,35 @@
 		}
 
 		return new WP_Error(
-			'rest_user_cannot_view',
-			__( 'Sorry, you are not allowed to view themes.' ),
+			'rest_cannot_view_active_theme',
+			__( 'Sorry, you are not allowed to view the active theme.' ),
 			array( 'status' => rest_authorization_required_code() )
 		);
 	}
 
 	/**
+	 * Retrieves a single theme.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
+	 */
+	public function get_item( $request ) {
+		$wp_theme = wp_get_theme( $request['stylesheet'] );
+		if ( ! $wp_theme->exists() ) {
+			return new WP_Error(
+				'rest_theme_not_found',
+				__( 'Theme not found.' ),
+				array( 'status' => 404 )
+			);
+		}
+		$data = $this->prepare_item_for_response( $wp_theme, $request );
+
+		return rest_ensure_response( $data );
+	}
+
+	/**
 	 * Retrieves a collection of themes.
 	 *
 	 * @since 5.0.0
@@ -84,20 +176,26 @@
 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 	 */
 	public function get_items( $request ) {
-		// Retrieve the list of registered collection query parameters.
-		$registered = $this->get_collection_params();
-		$themes     = array();
+		$themes = array();
+
+		$active_themes = wp_get_themes();
+		$current_theme = wp_get_theme();
+		$status        = $request['status'];
 
-		if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) {
-			$active_theme = wp_get_theme();
-			$active_theme = $this->prepare_item_for_response( $active_theme, $request );
-			$themes[]     = $this->prepare_response_for_collection( $active_theme );
+		foreach ( $active_themes as $theme_name => $theme ) {
+			$theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
+			if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) {
+				continue;
+			}
+
+			$prepared = $this->prepare_item_for_response( $theme, $request );
+			$themes[] = $this->prepare_response_for_collection( $prepared );
 		}
 
 		$response = rest_ensure_response( $themes );
 
 		$response->header( 'X-WP-Total', count( $themes ) );
-		$response->header( 'X-WP-TotalPages', count( $themes ) );
+		$response->header( 'X-WP-TotalPages', 1 );
 
 		return $response;
 	}
@@ -166,7 +264,12 @@
 			}
 		}
 
-		if ( rest_is_field_included( 'theme_supports', $fields ) ) {
+		$current_theme = wp_get_theme();
+		if ( rest_is_field_included( 'status', $fields ) ) {
+			$data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
+		}
+
+		if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) {
 			foreach ( get_registered_theme_features() as $feature => $config ) {
 				if ( ! is_array( $config['show_in_rest'] ) ) {
 					continue;
@@ -206,6 +309,8 @@
 		// Wrap the data in a response object.
 		$response = rest_ensure_response( $data );
 
+		$response->add_links( $this->prepare_links( $theme ) );
+
 		/**
 		 * Filters theme data returned from the REST API.
 		 *
@@ -219,6 +324,38 @@
 	}
 
 	/**
+	 * Prepares links for the request.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_Theme $theme Theme data.
+	 * @return array Links for the given block type.
+	 */
+	protected function prepare_links( $theme ) {
+		return array(
+			'self'       => array(
+				'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ),
+			),
+			'collection' => array(
+				'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
+			),
+		);
+	}
+
+	/**
+	 * Helper function to compare two themes.
+	 *
+	 * @since 5.7.0
+	 *
+	 * @param WP_Theme $theme_a First theme to compare.
+	 * @param WP_Theme $theme_b Second theme to compare.
+	 * @return bool
+	 */
+	protected function is_same_theme( $theme_a, $theme_b ) {
+		return $theme_a->get_stylesheet() === $theme_b->get_stylesheet();
+	}
+
+	/**
 	 * Prepares the theme support value for inclusion in the REST API response.
 	 *
 	 * @since 5.5.0
@@ -399,6 +536,11 @@
 					'type'        => 'string',
 					'readonly'    => true,
 				),
+				'status'         => array(
+					'description' => __( 'A named status for the theme.' ),
+					'type'        => 'string',
+					'enum'        => array( 'inactive', 'active' ),
+				),
 			),
 		);
 
@@ -425,21 +567,19 @@
 	 * @return array Collection parameters.
 	 */
 	public function get_collection_params() {
-		$query_params = parent::get_collection_params();
-
-		$query_params['status'] = array(
-			'description'       => __( 'Limit result set to themes assigned one or more statuses.' ),
-			'type'              => 'array',
-			'items'             => array(
-				'enum' => array( 'active' ),
-				'type' => 'string',
+		$query_params = array(
+			'status' => array(
+				'description' => __( 'Limit result set to themes assigned one or more statuses.' ),
+				'type'        => 'array',
+				'items'       => array(
+					'enum' => array( 'active', 'inactive' ),
+					'type' => 'string',
+				),
 			),
-			'required'          => true,
-			'sanitize_callback' => array( $this, 'sanitize_theme_status' ),
 		);
 
 		/**
-		 * Filter collection parameters for the themes controller.
+		 * Filters REST API collection parameters for the themes controller.
 		 *
 		 * @since 5.0.0
 		 *
@@ -452,6 +592,7 @@
 	 * Sanitizes and validates the list of theme status.
 	 *
 	 * @since 5.0.0
+	 * @deprecated 5.7.0
 	 *
 	 * @param string|array    $statuses  One or more theme statuses.
 	 * @param WP_REST_Request $request   Full details about the request.
@@ -459,6 +600,8 @@
 	 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
 	 */
 	public function sanitize_theme_status( $statuses, $request, $parameter ) {
+		_deprecated_function( __METHOD__, '5.7.0' );
+
 		$statuses = wp_parse_slug_list( $statuses );
 
 		foreach ( $statuses as $status ) {