PluginProbe
Gutenberg / 4.0.1
Gutenberg v4.0.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / rest-api.php

rest-api.php in Gutenberg 4.0.1, at lib/rest-api.php

476 lines 16.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHP and WordPress configuration compatibility functions for the Gutenberg
4 * editor plugin changes related to REST API.
5 *
6 * @package gutenberg
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 die( 'Silence is golden.' );
11 }
12
13 /**
14 * Registers the REST API routes needed by the Gutenberg editor.
15 *
16 * @since 2.8.0
17 */
18 function gutenberg_register_rest_routes() {
19 $controller = new WP_REST_Block_Renderer_Controller();
20 $controller->register_routes();
21
22 /**
23 * Filters the search handlers to use in the REST search controller.
24 *
25 * @since 3.3.0
26 *
27 * @param array $search_handlers List of search handlers to use in the controller. Each search
28 * handler instance must extend the `WP_REST_Search_Handler` class.
29 * Default is only a handler for posts.
30 */
31 $search_handlers = apply_filters( 'wp_rest_search_handlers', array( new WP_REST_Post_Search_Handler() ) );
32
33 $controller = new WP_REST_Search_Controller( $search_handlers );
34 $controller->register_routes();
35
36 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
37 $class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
38
39 // Check if the class exists and is a subclass of WP_REST_Controller.
40 if ( ! is_subclass_of( $class, 'WP_REST_Controller' ) ) {
41 continue;
42 }
43
44 // Initialize the Autosaves controller.
45 $autosaves_controller = new WP_REST_Autosaves_Controller( $post_type->name );
46 $autosaves_controller->register_routes();
47 }
48 }
49 add_action( 'rest_api_init', 'gutenberg_register_rest_routes' );
50
51 /**
52 * Make sure oEmbed REST Requests apply the WP Embed security mechanism for WordPress embeds.
53 *
54 * @see https://core.trac.wordpress.org/ticket/32522
55 *
56 * TODO: This is a temporary solution. Next step would be to edit the WP_oEmbed_Controller,
57 * once merged into Core.
58 *
59 * @since 2.3.0
60 *
61 * @param WP_HTTP_Response|WP_Error $response The REST Request response.
62 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
63 * @param WP_REST_Request $request Request used to generate the response.
64 * @return WP_HTTP_Response|object|WP_Error The REST Request response.
65 */
66 function gutenberg_filter_oembed_result( $response, $handler, $request ) {
67 if ( 'GET' !== $request->get_method() ) {
68 return $response;
69 }
70
71 if ( is_wp_error( $response ) && 'oembed_invalid_url' !== $response->get_error_code() ) {
72 return $response;
73 }
74
75 // External embeds.
76 if ( '/oembed/1.0/proxy' === $request->get_route() ) {
77 if ( is_wp_error( $response ) ) {
78 // It's possibly a local post, so lets try and retrieve it that way.
79 $post_id = url_to_postid( $_GET['url'] );
80 $data = get_oembed_response_data( $post_id, apply_filters( 'oembed_default_width', 600 ) );
81
82 if ( $data ) {
83 // It's a local post!
84 $response = (object) $data;
85 } else {
86 // Try using a classic embed, instead.
87 global $wp_embed;
88 $html = $wp_embed->shortcode( array(), $_GET['url'] );
89 if ( $html ) {
90 global $wp_scripts;
91 // Check if any scripts were enqueued by the shortcode, and
92 // include them in the response.
93 $enqueued_scripts = array();
94 foreach ( $wp_scripts->queue as $script ) {
95 $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src;
96 }
97 return array(
98 'provider_name' => __( 'Embed Handler', 'gutenberg' ),
99 'html' => $html,
100 'scripts' => $enqueued_scripts,
101 );
102 }
103 }
104 }
105
106 // Make sure the HTML is run through the oembed sanitisation routines.
107 $response->html = wp_oembed_get( $_GET['url'], $_GET );
108 }
109
110 return $response;
111 }
112 add_filter( 'rest_request_after_callbacks', 'gutenberg_filter_oembed_result', 10, 3 );
113
114 /**
115 * Add additional 'visibility' rest api field to taxonomies.
116 *
117 * Used so private taxonomies are not displayed in the UI.
118 *
119 * @see https://core.trac.wordpress.org/ticket/42707
120 */
121 function gutenberg_add_taxonomy_visibility_field() {
122 register_rest_field(
123 'taxonomy',
124 'visibility',
125 array(
126 'get_callback' => 'gutenberg_get_taxonomy_visibility_data',
127 'schema' => array(
128 'description' => __( 'The visibility settings for the taxonomy.', 'gutenberg' ),
129 'type' => 'object',
130 'context' => array( 'edit' ),
131 'readonly' => true,
132 'properties' => array(
133 'public' => array(
134 'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.', 'gutenberg' ),
135 'type' => 'boolean',
136 ),
137 'publicly_queryable' => array(
138 'description' => __( 'Whether the taxonomy is publicly queryable.', 'gutenberg' ),
139 'type' => 'boolean',
140 ),
141 'show_ui' => array(
142 'description' => __( 'Whether to generate a default UI for managing this taxonomy.', 'gutenberg' ),
143 'type' => 'boolean',
144 ),
145 'show_admin_column' => array(
146 'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.', 'gutenberg' ),
147 'type' => 'boolean',
148 ),
149 'show_in_nav_menus' => array(
150 'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.', 'gutenberg' ),
151 'type' => 'boolean',
152 ),
153 'show_in_quick_edit' => array(
154 'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.', 'gutenberg' ),
155 'type' => 'boolean',
156 ),
157 ),
158 ),
159 )
160 );
161 }
162
163 /**
164 * Gets taxonomy visibility property data.
165 *
166 * @see https://core.trac.wordpress.org/ticket/42707
167 *
168 * @param array $object Taxonomy data from REST API.
169 * @return array Array of taxonomy visibility data.
170 */
171 function gutenberg_get_taxonomy_visibility_data( $object ) {
172 // Just return existing data for up-to-date Core.
173 if ( isset( $object['visibility'] ) ) {
174 return $object['visibility'];
175 }
176
177 $taxonomy = get_taxonomy( $object['slug'] );
178
179 return array(
180 'public' => $taxonomy->public,
181 'publicly_queryable' => $taxonomy->publicly_queryable,
182 'show_ui' => $taxonomy->show_ui,
183 'show_admin_column' => $taxonomy->show_admin_column,
184 'show_in_nav_menus' => $taxonomy->show_in_nav_menus,
185 'show_in_quick_edit' => $taxonomy->show_ui,
186 );
187 }
188
189 add_action( 'rest_api_init', 'gutenberg_add_taxonomy_visibility_field' );
190
191 /**
192 * Add a permalink template to posts in the post REST API response.
193 *
194 * @see https://core.trac.wordpress.org/ticket/45017
195 *
196 * @param WP_REST_Response $response WP REST API response of a post.
197 * @param WP_Post $post The post being returned.
198 * @param WP_REST_Request $request WP REST API request.
199 * @return WP_REST_Response Response containing the permalink_template.
200 */
201 function gutenberg_add_permalink_template_to_posts( $response, $post, $request ) {
202 if ( 'edit' !== $request['context'] ) {
203 return $response;
204 }
205
206 if ( ! function_exists( 'get_sample_permalink' ) ) {
207 require_once ABSPATH . '/wp-admin/includes/post.php';
208 }
209
210 $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' );
211
212 $response->data['permalink_template'] = $sample_permalink[0];
213 $response->data['generated_slug'] = $sample_permalink[1];
214
215 return $response;
216 }
217
218 /**
219 * Add the block format version to post content in the post REST API response.
220 *
221 * @todo This will need to be registered to the schema too.
222 *
223 * @see https://core.trac.wordpress.org/ticket/43887
224 *
225 * @param WP_REST_Response $response WP REST API response of a post.
226 * @param WP_Post $post The post being returned.
227 * @param WP_REST_Request $request WP REST API request.
228 * @return WP_REST_Response Response containing the block_format.
229 */
230 function gutenberg_add_block_format_to_post_content( $response, $post, $request ) {
231 if ( 'edit' !== $request['context'] ) {
232 return $response;
233 }
234
235 $response_data = $response->get_data();
236 if ( isset( $response_data['content'] ) && is_array( $response_data['content'] ) && isset( $response_data['content']['raw'] ) ) {
237 $response_data['content']['block_format'] = gutenberg_content_block_version( $response_data['content']['raw'] );
238 $response->set_data( $response_data );
239 }
240
241 return $response;
242 }
243
244 /**
245 * Include target schema attributes to links, based on whether the user can.
246 *
247 * @see https://core.trac.wordpress.org/ticket/45014
248 *
249 * @param WP_REST_Response $response WP REST API response of a post.
250 * @param WP_Post $post The post being returned.
251 * @param WP_REST_Request $request WP REST API request.
252 * @return WP_REST_Response Response containing the new links.
253 */
254 function gutenberg_add_target_schema_to_links( $response, $post, $request ) {
255 $new_links = array();
256 $orig_links = $response->get_links();
257 $post_type = get_post_type_object( $post->post_type );
258 $orig_href = ! empty( $orig_links['self'][0]['href'] ) ? $orig_links['self'][0]['href'] : null;
259 if ( 'edit' === $request['context'] && current_user_can( 'unfiltered_html' ) ) {
260 $new_links['https://api.w.org/action-unfiltered-html'] = array(
261 array(
262 'title' => __( 'The current user can post HTML markup and JavaScript.', 'gutenberg' ),
263 'href' => $orig_href,
264 'targetSchema' => array(
265 'type' => 'object',
266 'properties' => array(
267 'unfiltered_html' => array(
268 'type' => 'boolean',
269 ),
270 ),
271 ),
272 ),
273 );
274 }
275
276 $response->add_links( $new_links );
277 return $response;
278 }
279
280 /**
281 * Whenever a post type is registered, ensure we're hooked into it's WP REST API response.
282 *
283 * @param string $post_type The newly registered post type.
284 * @return string That same post type.
285 */
286 function gutenberg_register_post_prepare_functions( $post_type ) {
287 add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_permalink_template_to_posts', 10, 3 );
288 add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_block_format_to_post_content', 10, 3 );
289 add_filter( "rest_prepare_{$post_type}", 'gutenberg_add_target_schema_to_links', 10, 3 );
290 add_filter( "rest_{$post_type}_collection_params", 'gutenberg_filter_post_collection_parameters', 10, 2 );
291 add_filter( "rest_{$post_type}_query", 'gutenberg_filter_post_query_arguments', 10, 2 );
292 return $post_type;
293 }
294 add_filter( 'registered_post_type', 'gutenberg_register_post_prepare_functions' );
295
296 /**
297 * Whenever a taxonomy is registered, ensure we're hooked into its WP REST API response.
298 *
299 * @param string $taxonomy The newly registered taxonomy.
300 */
301 function gutenberg_register_taxonomy_prepare_functions( $taxonomy ) {
302 add_filter( "rest_{$taxonomy}_collection_params", 'gutenberg_filter_term_collection_parameters', 10, 2 );
303 add_filter( "rest_{$taxonomy}_query", 'gutenberg_filter_term_query_arguments', 10, 2 );
304 }
305 add_filter( 'registered_taxonomy', 'gutenberg_register_taxonomy_prepare_functions' );
306
307 /**
308 * Ensure that the wp-json index contains the 'theme-supports' setting as
309 * part of its site info elements.
310 *
311 * @see https://core.trac.wordpress.org/ticket/45016
312 *
313 * @param WP_REST_Response $response WP REST API response of the wp-json index.
314 * @return WP_REST_Response Response that contains theme-supports.
315 */
316 function gutenberg_ensure_wp_json_has_theme_supports( $response ) {
317 $site_info = $response->get_data();
318 if ( ! array_key_exists( 'theme_supports', $site_info ) ) {
319 $site_info['theme_supports'] = array();
320 }
321 if ( ! array_key_exists( 'formats', $site_info['theme_supports'] ) ) {
322 $formats = get_theme_support( 'post-formats' );
323 $formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
324 $formats = array_merge( array( 'standard' ), $formats );
325
326 $site_info['theme_supports']['formats'] = $formats;
327 }
328 if ( ! array_key_exists( 'post-thumbnails', $site_info['theme_supports'] ) ) {
329 $post_thumbnails = get_theme_support( 'post-thumbnails' );
330 if ( $post_thumbnails ) {
331 // $post_thumbnails can contain a nested array of post types.
332 // e.g. array( array( 'post', 'page' ) ).
333 $site_info['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true;
334 }
335 }
336 $response->set_data( $site_info );
337 return $response;
338 }
339 add_filter( 'rest_index', 'gutenberg_ensure_wp_json_has_theme_supports' );
340
341 /**
342 * Handle any necessary checks early.
343 *
344 * @param WP_HTTP_Response $response Result to send to the client. Usually a WP_REST_Response.
345 * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server).
346 * @param WP_REST_Request $request Request used to generate the response.
347 */
348 function gutenberg_handle_early_callback_checks( $response, $handler, $request ) {
349 if ( 0 === strpos( $request->get_route(), '/wp/v2/' ) ) {
350 $can_unbounded_query = false;
351 $types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
352 foreach ( $types as $type ) {
353 if ( current_user_can( $type->cap->edit_posts ) ) {
354 $can_unbounded_query = true;
355 }
356 }
357 if ( $request['per_page'] < 0 ) {
358 if ( ! $can_unbounded_query ) {
359 return new WP_Error( 'rest_forbidden_per_page', __( 'Sorry, you are not allowed make unbounded queries.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) );
360 }
361 }
362 }
363 return $response;
364 }
365 add_filter( 'rest_request_before_callbacks', 'gutenberg_handle_early_callback_checks', 10, 3 );
366
367 /**
368 * Include additional query parameters on the posts query endpoint.
369 *
370 * @see https://core.trac.wordpress.org/ticket/43998
371 *
372 * @param array $query_params JSON Schema-formatted collection parameters.
373 * @param WP_Post_Type $post_type Post type object being accessed.
374 * @return array
375 */
376 function gutenberg_filter_post_collection_parameters( $query_params, $post_type ) {
377 if (
378 isset( $query_params['per_page'] ) &&
379 ( $post_type->hierarchical || 'wp_block' === $post_type->name )
380 ) {
381 // Change from '1' to '-1', which means unlimited.
382 $query_params['per_page']['minimum'] = -1;
383 // Default sanitize callback is 'absint', which won't work in our case.
384 $query_params['per_page']['sanitize_callback'] = 'rest_sanitize_request_arg';
385 }
386 return $query_params;
387 }
388
389 /**
390 * Filter post collection query parameters to include specific behavior.
391 *
392 * @see https://core.trac.wordpress.org/ticket/43998
393 *
394 * @param array $prepared_args Array of arguments for WP_Query.
395 * @param WP_REST_Request $request The current request.
396 * @return array
397 */
398 function gutenberg_filter_post_query_arguments( $prepared_args, $request ) {
399 if (
400 is_post_type_hierarchical( $prepared_args['post_type'] ) ||
401 'wp_block' === $prepared_args['post_type']
402 ) {
403 // Avoid triggering 'rest_post_invalid_page_number' error
404 // which will need to be addressed in https://core.trac.wordpress.org/ticket/43998.
405 if ( -1 === $prepared_args['posts_per_page'] ) {
406 $prepared_args['posts_per_page'] = 100000;
407 }
408 }
409 return $prepared_args;
410 }
411
412 /**
413 * Include additional query parameters on the terms query endpoint.
414 *
415 * @see https://core.trac.wordpress.org/ticket/43998
416 *
417 * @param array $query_params JSON Schema-formatted collection parameters.
418 * @param object $taxonomy Taxonomy being accessed.
419 * @return array
420 */
421 function gutenberg_filter_term_collection_parameters( $query_params, $taxonomy ) {
422 if ( $taxonomy->show_in_rest
423 && ( false === $taxonomy->rest_controller_class
424 || 'WP_REST_Terms_Controller' === $taxonomy->rest_controller_class )
425 && isset( $query_params['per_page'] ) ) {
426 // Change from '1' to '-1', which means unlimited.
427 $query_params['per_page']['minimum'] = -1;
428 // Default sanitize callback is 'absint', which won't work in our case.
429 $query_params['per_page']['sanitize_callback'] = 'rest_sanitize_request_arg';
430 }
431 return $query_params;
432 }
433
434 /**
435 * Filter term collection query parameters to include specific behavior.
436 *
437 * @see https://core.trac.wordpress.org/ticket/43998
438 *
439 * @param array $prepared_args Array of arguments for WP_Term_Query.
440 * @param WP_REST_Request $request The current request.
441 * @return array
442 */
443 function gutenberg_filter_term_query_arguments( $prepared_args, $request ) {
444 // Can't check the actual taxonomy here because it's not
445 // passed through in $prepared_args (or the filter generally).
446 if ( 0 === strpos( $request->get_route(), '/wp/v2/' ) ) {
447 if ( -1 === $prepared_args['number'] ) {
448 // This should be unset( $prepared_args['number'] )
449 // but WP_REST_Terms Controller needs to be updated to support
450 // unbounded queries.
451 // Will be addressed in https://core.trac.wordpress.org/ticket/43998.
452 $prepared_args['number'] = 100000;
453 }
454 }
455 return $prepared_args;
456 }
457
458 /**
459 * Include additional query parameters on the user query endpoint.
460 *
461 * @see https://core.trac.wordpress.org/ticket/43998
462 *
463 * @param array $query_params JSON Schema-formatted collection parameters.
464 * @return array
465 */
466 function gutenberg_filter_user_collection_parameters( $query_params ) {
467 if ( isset( $query_params['per_page'] ) ) {
468 // Change from '1' to '-1', which means unlimited.
469 $query_params['per_page']['minimum'] = -1;
470 // Default sanitize callback is 'absint', which won't work in our case.
471 $query_params['per_page']['sanitize_callback'] = 'rest_sanitize_request_arg';
472 }
473 return $query_params;
474 }
475 add_filter( 'rest_user_collection_params', 'gutenberg_filter_user_collection_parameters' );
476