wp/wp-includes/rest-api/endpoints/class-wp-rest-pattern-directory-controller.php
changeset 19 3d72ae0968f4
parent 18 be944660c56a
child 21 48c4eec2b7e6
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
    23 	 * Constructs the controller.
    23 	 * Constructs the controller.
    24 	 *
    24 	 *
    25 	 * @since 5.8.0
    25 	 * @since 5.8.0
    26 	 */
    26 	 */
    27 	public function __construct() {
    27 	public function __construct() {
    28 		$this->namespace     = 'wp/v2';
    28 		$this->namespace = 'wp/v2';
    29 			$this->rest_base = 'pattern-directory';
    29 		$this->rest_base = 'pattern-directory';
    30 	}
    30 	}
    31 
    31 
    32 	/**
    32 	/**
    33 	 * Registers the necessary REST API routes.
    33 	 * Registers the necessary REST API routes.
    34 	 *
    34 	 *
    49 			)
    49 			)
    50 		);
    50 		);
    51 	}
    51 	}
    52 
    52 
    53 	/**
    53 	/**
    54 	 * Checks whether a given request has permission to view the local pattern directory.
    54 	 * Checks whether a given request has permission to view the local block pattern directory.
    55 	 *
    55 	 *
    56 	 * @since 5.8.0
    56 	 * @since 5.8.0
    57 	 *
    57 	 *
    58 	 * @param WP_REST_Request $request Full details about the request.
    58 	 * @param WP_REST_Request $request Full details about the request.
    59 	 * @return true|WP_Error True if the request has permission, WP_Error object otherwise.
    59 	 * @return true|WP_Error True if the request has permission, WP_Error object otherwise.
    78 
    78 
    79 	/**
    79 	/**
    80 	 * Search and retrieve block patterns metadata
    80 	 * Search and retrieve block patterns metadata
    81 	 *
    81 	 *
    82 	 * @since 5.8.0
    82 	 * @since 5.8.0
       
    83 	 * @since 6.0.0 Added 'slug' to request.
    83 	 *
    84 	 *
    84 	 * @param WP_REST_Request $request Full details about the request.
    85 	 * @param WP_REST_Request $request Full details about the request.
    85 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    86 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
    86 	 */
    87 	 */
    87 	public function get_items( $request ) {
    88 	public function get_items( $request ) {
    98 		);
    99 		);
    99 
   100 
   100 		$category_id = $request['category'];
   101 		$category_id = $request['category'];
   101 		$keyword_id  = $request['keyword'];
   102 		$keyword_id  = $request['keyword'];
   102 		$search_term = $request['search'];
   103 		$search_term = $request['search'];
       
   104 		$slug        = $request['slug'];
   103 
   105 
   104 		if ( $category_id ) {
   106 		if ( $category_id ) {
   105 			$query_args['pattern-categories'] = $category_id;
   107 			$query_args['pattern-categories'] = $category_id;
   106 		}
   108 		}
   107 
   109 
   111 
   113 
   112 		if ( $search_term ) {
   114 		if ( $search_term ) {
   113 			$query_args['search'] = $search_term;
   115 			$query_args['search'] = $search_term;
   114 		}
   116 		}
   115 
   117 
   116 		/*
   118 		if ( $slug ) {
   117 		 * Include a hash of the query args, so that different requests are stored in
   119 			$query_args['slug'] = $slug;
   118 		 * separate caches.
   120 		}
   119 		 *
   121 
   120 		 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
   122 		$transient_key = $this->get_transient_key( $query_args );
   121 		 * under the character limit for `_site_transient_timeout_{...}` keys.
       
   122 		 *
       
   123 		 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
       
   124 		 */
       
   125 		$transient_key = 'wp_remote_block_patterns_' . md5( implode( '-', $query_args ) );
       
   126 
   123 
   127 		/*
   124 		/*
   128 		 * Use network-wide transient to improve performance. The locale is the only site
   125 		 * Use network-wide transient to improve performance. The locale is the only site
   129 		 * configuration that affects the response, and it's included in the transient key.
   126 		 * configuration that affects the response, and it's included in the transient key.
   130 		 */
   127 		 */
   131 		$raw_patterns = get_site_transient( $transient_key );
   128 		$raw_patterns = get_site_transient( $transient_key );
   132 
   129 
   133 		if ( ! $raw_patterns ) {
   130 		if ( ! $raw_patterns ) {
   134 			$api_url = add_query_arg(
   131 			$api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args );
   135 				array_map( 'rawurlencode', $query_args ),
       
   136 				'http://api.wordpress.org/patterns/1.0/'
       
   137 			);
       
   138 
       
   139 			if ( wp_http_supports( array( 'ssl' ) ) ) {
   132 			if ( wp_http_supports( array( 'ssl' ) ) ) {
   140 				$api_url = set_url_scheme( $api_url, 'https' );
   133 				$api_url = set_url_scheme( $api_url, 'https' );
   141 			}
   134 			}
   142 
   135 
   143 			/*
   136 			/*
   157 			} elseif ( ! is_array( $raw_patterns ) ) {
   150 			} elseif ( ! is_array( $raw_patterns ) ) {
   158 				// HTTP request succeeded, but response data is invalid.
   151 				// HTTP request succeeded, but response data is invalid.
   159 				$raw_patterns = new WP_Error(
   152 				$raw_patterns = new WP_Error(
   160 					'pattern_api_failed',
   153 					'pattern_api_failed',
   161 					sprintf(
   154 					sprintf(
   162 					/* translators: %s: Support forums URL. */
   155 						/* translators: %s: Support forums URL. */
   163 						__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
   156 						__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server&#8217;s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
   164 						__( 'https://wordpress.org/support/forums/' )
   157 						__( 'https://wordpress.org/support/forums/' )
   165 					),
   158 					),
   166 					array(
   159 					array(
   167 						'response' => wp_remote_retrieve_body( $wporg_response ),
   160 						'response' => wp_remote_retrieve_body( $wporg_response ),
   194 
   187 
   195 		return new WP_REST_Response( $response );
   188 		return new WP_REST_Response( $response );
   196 	}
   189 	}
   197 
   190 
   198 	/**
   191 	/**
   199 	 * Prepare a raw pattern before it's output in an API response.
   192 	 * Prepare a raw block pattern before it gets output in a REST API response.
   200 	 *
   193 	 *
   201 	 * @since 5.8.0
   194 	 * @since 5.8.0
   202 	 *
   195 	 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support.
   203 	 * @param object          $raw_pattern A pattern from api.wordpress.org, before any changes.
   196 	 *
   204 	 * @param WP_REST_Request $request     Request object.
   197 	 * @param object          $item    Raw pattern from api.wordpress.org, before any changes.
       
   198 	 * @param WP_REST_Request $request Request object.
   205 	 * @return WP_REST_Response
   199 	 * @return WP_REST_Response
   206 	 */
   200 	 */
   207 	public function prepare_item_for_response( $raw_pattern, $request ) {
   201 	public function prepare_item_for_response( $item, $request ) {
       
   202 		// Restores the more descriptive, specific name for use within this method.
       
   203 		$raw_pattern      = $item;
   208 		$prepared_pattern = array(
   204 		$prepared_pattern = array(
   209 			'id'             => absint( $raw_pattern->id ),
   205 			'id'             => absint( $raw_pattern->id ),
   210 			'title'          => sanitize_text_field( $raw_pattern->title->rendered ),
   206 			'title'          => sanitize_text_field( $raw_pattern->title->rendered ),
   211 			'content'        => wp_kses_post( $raw_pattern->pattern_content ),
   207 			'content'        => wp_kses_post( $raw_pattern->pattern_content ),
   212 			'categories'     => array_map( 'sanitize_title', $raw_pattern->category_slugs ),
   208 			'categories'     => array_map( 'sanitize_title', $raw_pattern->category_slugs ),
   213 			'keywords'       => array_map( 'sanitize_title', $raw_pattern->keyword_slugs ),
   209 			'keywords'       => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ),
   214 			'description'    => sanitize_text_field( $raw_pattern->meta->wpop_description ),
   210 			'description'    => sanitize_text_field( $raw_pattern->meta->wpop_description ),
   215 			'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ),
   211 			'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ),
   216 		);
   212 		);
   217 
   213 
   218 		$prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request );
   214 		$prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request );
   219 
   215 
   220 		$response = new WP_REST_Response( $prepared_pattern );
   216 		$response = new WP_REST_Response( $prepared_pattern );
   221 
   217 
   222 		/**
   218 		/**
   223 		 * Filters the REST API response for a pattern.
   219 		 * Filters the REST API response for a block pattern.
   224 		 *
   220 		 *
   225 		 * @since 5.8.0
   221 		 * @since 5.8.0
   226 		 *
   222 		 *
   227 		 * @param WP_REST_Response $response    The response object.
   223 		 * @param WP_REST_Response $response    The response object.
   228 		 * @param object           $raw_pattern The unprepared pattern.
   224 		 * @param object           $raw_pattern The unprepared block pattern.
   229 		 * @param WP_REST_Request  $request     The request object.
   225 		 * @param WP_REST_Request  $request     The request object.
   230 		 */
   226 		 */
   231 		return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request );
   227 		return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request );
   232 	}
   228 	}
   233 
   229 
   234 	/**
   230 	/**
   235 	 * Retrieves the pattern's schema, conforming to JSON Schema.
   231 	 * Retrieves the block pattern's schema, conforming to JSON Schema.
   236 	 *
   232 	 *
   237 	 * @since 5.8.0
   233 	 * @since 5.8.0
   238 	 *
   234 	 *
   239 	 * @return array Item schema data.
   235 	 * @return array Item schema data.
   240 	 */
   236 	 */
   250 			'properties' => array(
   246 			'properties' => array(
   251 				'id'             => array(
   247 				'id'             => array(
   252 					'description' => __( 'The pattern ID.' ),
   248 					'description' => __( 'The pattern ID.' ),
   253 					'type'        => 'integer',
   249 					'type'        => 'integer',
   254 					'minimum'     => 1,
   250 					'minimum'     => 1,
   255 					'context'     => array( 'view', 'embed' ),
   251 					'context'     => array( 'view', 'edit', 'embed' ),
   256 				),
   252 				),
   257 
   253 
   258 				'title'          => array(
   254 				'title'          => array(
   259 					'description' => __( 'The pattern title, in human readable format.' ),
   255 					'description' => __( 'The pattern title, in human readable format.' ),
   260 					'type'        => 'string',
   256 					'type'        => 'string',
   261 					'minLength'   => 1,
   257 					'minLength'   => 1,
   262 					'context'     => array( 'view', 'embed' ),
   258 					'context'     => array( 'view', 'edit', 'embed' ),
   263 				),
   259 				),
   264 
   260 
   265 				'content'        => array(
   261 				'content'        => array(
   266 					'description' => __( 'The pattern content.' ),
   262 					'description' => __( 'The pattern content.' ),
   267 					'type'        => 'string',
   263 					'type'        => 'string',
   268 					'minLength'   => 1,
   264 					'minLength'   => 1,
   269 					'context'     => array( 'view', 'embed' ),
   265 					'context'     => array( 'view', 'edit', 'embed' ),
   270 				),
   266 				),
   271 
   267 
   272 				'categories'     => array(
   268 				'categories'     => array(
   273 					'description' => __( "The pattern's category slugs." ),
   269 					'description' => __( "The pattern's category slugs." ),
   274 					'type'        => 'array',
   270 					'type'        => 'array',
   275 					'uniqueItems' => true,
   271 					'uniqueItems' => true,
   276 					'items'       => array( 'type' => 'string' ),
   272 					'items'       => array( 'type' => 'string' ),
   277 					'context'     => array( 'view', 'embed' ),
   273 					'context'     => array( 'view', 'edit', 'embed' ),
   278 				),
   274 				),
   279 
   275 
   280 				'keywords'       => array(
   276 				'keywords'       => array(
   281 					'description' => __( "The pattern's keyword slugs." ),
   277 					'description' => __( "The pattern's keywords." ),
   282 					'type'        => 'array',
   278 					'type'        => 'array',
   283 					'uniqueItems' => true,
   279 					'uniqueItems' => true,
   284 					'items'       => array( 'type' => 'string' ),
   280 					'items'       => array( 'type' => 'string' ),
   285 					'context'     => array( 'view', 'embed' ),
   281 					'context'     => array( 'view', 'edit', 'embed' ),
   286 				),
   282 				),
   287 
   283 
   288 				'description'    => array(
   284 				'description'    => array(
   289 					'description' => __( 'A description of the pattern.' ),
   285 					'description' => __( 'A description of the pattern.' ),
   290 					'type'        => 'string',
   286 					'type'        => 'string',
   291 					'minLength'   => 1,
   287 					'minLength'   => 1,
   292 					'context'     => array( 'view', 'embed' ),
   288 					'context'     => array( 'view', 'edit', 'embed' ),
   293 				),
   289 				),
   294 
   290 
   295 				'viewport_width' => array(
   291 				'viewport_width' => array(
   296 					'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
   292 					'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
   297 					'type'        => 'integer',
   293 					'type'        => 'integer',
   298 					'context'     => array( 'view', 'embed' ),
   294 					'context'     => array( 'view', 'edit', 'embed' ),
   299 				),
   295 				),
   300 			),
   296 			),
   301 		);
   297 		);
   302 
   298 
   303 		return $this->add_additional_fields_schema( $this->schema );
   299 		return $this->add_additional_fields_schema( $this->schema );
   304 	}
   300 	}
   305 
   301 
   306 	/**
   302 	/**
   307 	 * Retrieves the search params for the patterns collection.
   303 	 * Retrieves the search parameters for the block pattern's collection.
   308 	 *
   304 	 *
   309 	 * @since 5.8.0
   305 	 * @since 5.8.0
   310 	 *
   306 	 *
   311 	 * @return array Collection parameters.
   307 	 * @return array Collection parameters.
   312 	 */
   308 	 */
   330 			'description' => __( 'Limit results to those matching a keyword ID.' ),
   326 			'description' => __( 'Limit results to those matching a keyword ID.' ),
   331 			'type'        => 'integer',
   327 			'type'        => 'integer',
   332 			'minimum'     => 1,
   328 			'minimum'     => 1,
   333 		);
   329 		);
   334 
   330 
       
   331 		$query_params['slug'] = array(
       
   332 			'description' => __( 'Limit results to those matching a pattern (slug).' ),
       
   333 			'type'        => 'array',
       
   334 		);
       
   335 
   335 		/**
   336 		/**
   336 		 * Filter collection parameters for the pattern directory controller.
   337 		 * Filter collection parameters for the block pattern directory controller.
   337 		 *
   338 		 *
   338 		 * @since 5.8.0
   339 		 * @since 5.8.0
   339 		 *
   340 		 *
   340 		 * @param array $query_params JSON Schema-formatted collection parameters.
   341 		 * @param array $query_params JSON Schema-formatted collection parameters.
   341 		 */
   342 		 */
   342 		return apply_filters( 'rest_pattern_directory_collection_params', $query_params );
   343 		return apply_filters( 'rest_pattern_directory_collection_params', $query_params );
   343 	}
   344 	}
       
   345 
       
   346 	/*
       
   347 	 * Include a hash of the query args, so that different requests are stored in
       
   348 	 * separate caches.
       
   349 	 *
       
   350 	 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
       
   351 	 * under the character limit for `_site_transient_timeout_{...}` keys.
       
   352 	 *
       
   353 	 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
       
   354 	 *
       
   355 	 * @since 6.0.0
       
   356 	 *
       
   357 	 * @param array $query_args Query arguments to generate a transient key from.
       
   358 	 * @return string Transient key.
       
   359 	 */
       
   360 	protected function get_transient_key( $query_args ) {
       
   361 
       
   362 		if ( isset( $query_args['slug'] ) ) {
       
   363 			// This is an additional precaution because the "sort" function expects an array.
       
   364 			$query_args['slug'] = wp_parse_list( $query_args['slug'] );
       
   365 
       
   366 			// Empty arrays should not affect the transient key.
       
   367 			if ( empty( $query_args['slug'] ) ) {
       
   368 				unset( $query_args['slug'] );
       
   369 			} else {
       
   370 				// Sort the array so that the transient key doesn't depend on the order of slugs.
       
   371 				sort( $query_args['slug'] );
       
   372 			}
       
   373 		}
       
   374 
       
   375 		return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
       
   376 	}
   344 }
   377 }