245 $parameter_mappings = array( |
245 $parameter_mappings = array( |
246 'author' => 'author__in', |
246 'author' => 'author__in', |
247 'author_exclude' => 'author__not_in', |
247 'author_exclude' => 'author__not_in', |
248 'exclude' => 'post__not_in', |
248 'exclude' => 'post__not_in', |
249 'include' => 'post__in', |
249 'include' => 'post__in', |
|
250 'ignore_sticky' => 'ignore_sticky_posts', |
250 'menu_order' => 'menu_order', |
251 'menu_order' => 'menu_order', |
251 'offset' => 'offset', |
252 'offset' => 'offset', |
252 'order' => 'order', |
253 'order' => 'order', |
253 'orderby' => 'orderby', |
254 'orderby' => 'orderby', |
254 'page' => 'paged', |
255 'page' => 'paged', |
335 */ |
336 */ |
336 $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); |
337 $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); |
337 } |
338 } |
338 } |
339 } |
339 |
340 |
|
341 /* |
|
342 * Honor the original REST API `post__in` behavior. Don't prepend sticky posts |
|
343 * when `post__in` has been specified. |
|
344 */ |
|
345 if ( ! empty( $args['post__in'] ) ) { |
|
346 unset( $args['ignore_sticky_posts'] ); |
|
347 } |
|
348 |
|
349 if ( |
|
350 isset( $registered['search_semantics'], $request['search_semantics'] ) |
|
351 && 'exact' === $request['search_semantics'] |
|
352 ) { |
|
353 $args['exact'] = true; |
|
354 } |
|
355 |
340 $args = $this->prepare_tax_query( $args, $request ); |
356 $args = $this->prepare_tax_query( $args, $request ); |
|
357 |
|
358 if ( isset( $registered['format'], $request['format'] ) ) { |
|
359 $formats = $request['format']; |
|
360 /* |
|
361 * The relation needs to be set to `OR` since the request can contain |
|
362 * two separate conditions. The user may be querying for items that have |
|
363 * either the `standard` format or a specific format. |
|
364 */ |
|
365 $formats_query = array( 'relation' => 'OR' ); |
|
366 |
|
367 /* |
|
368 * The default post format, `standard`, is not stored in the database. |
|
369 * If `standard` is part of the request, the query needs to exclude all post items that |
|
370 * have a format assigned. |
|
371 */ |
|
372 if ( in_array( 'standard', $formats, true ) ) { |
|
373 $formats_query[] = array( |
|
374 'taxonomy' => 'post_format', |
|
375 'field' => 'slug', |
|
376 'operator' => 'NOT EXISTS', |
|
377 ); |
|
378 // Remove the `standard` format, since it cannot be queried. |
|
379 unset( $formats[ array_search( 'standard', $formats, true ) ] ); |
|
380 } |
|
381 |
|
382 // Add any remaining formats to the formats query. |
|
383 if ( ! empty( $formats ) ) { |
|
384 // Add the `post-format-` prefix. |
|
385 $terms = array_map( |
|
386 static function ( $format ) { |
|
387 return "post-format-$format"; |
|
388 }, |
|
389 $formats |
|
390 ); |
|
391 |
|
392 $formats_query[] = array( |
|
393 'taxonomy' => 'post_format', |
|
394 'field' => 'slug', |
|
395 'terms' => $terms, |
|
396 'operator' => 'IN', |
|
397 ); |
|
398 } |
|
399 |
|
400 // Enable filtering by both post formats and other taxonomies by combining them with `AND`. |
|
401 if ( isset( $args['tax_query'] ) ) { |
|
402 $args['tax_query'][] = array( |
|
403 'relation' => 'AND', |
|
404 $formats_query, |
|
405 ); |
|
406 } else { |
|
407 $args['tax_query'] = $formats_query; |
|
408 } |
|
409 } |
341 |
410 |
342 // Force the post_type argument, since it's not a user input variable. |
411 // Force the post_type argument, since it's not a user input variable. |
343 $args['post_type'] = $this->post_type; |
412 $args['post_type'] = $this->post_type; |
|
413 |
|
414 $is_head_request = $request->is_method( 'HEAD' ); |
|
415 if ( $is_head_request ) { |
|
416 // Force the 'fields' argument. For HEAD requests, only post IDs are required to calculate pagination. |
|
417 $args['fields'] = 'ids'; |
|
418 // Disable priming post meta for HEAD requests to improve performance. |
|
419 $args['update_post_term_cache'] = false; |
|
420 $args['update_post_meta_cache'] = false; |
|
421 } |
344 |
422 |
345 /** |
423 /** |
346 * Filters WP_Query arguments when querying posts via the REST API. |
424 * Filters WP_Query arguments when querying posts via the REST API. |
347 * |
425 * |
348 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. |
426 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. |
372 // Allow access to all password protected posts if the context is edit. |
450 // Allow access to all password protected posts if the context is edit. |
373 if ( 'edit' === $request['context'] ) { |
451 if ( 'edit' === $request['context'] ) { |
374 add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); |
452 add_filter( 'post_password_required', array( $this, 'check_password_required' ), 10, 2 ); |
375 } |
453 } |
376 |
454 |
377 $posts = array(); |
455 if ( ! $is_head_request ) { |
378 |
456 $posts = array(); |
379 update_post_author_caches( $query_result ); |
457 |
380 update_post_parent_caches( $query_result ); |
458 update_post_author_caches( $query_result ); |
381 |
459 update_post_parent_caches( $query_result ); |
382 if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { |
460 |
383 update_post_thumbnail_cache( $posts_query ); |
461 if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { |
384 } |
462 update_post_thumbnail_cache( $posts_query ); |
385 |
463 } |
386 foreach ( $query_result as $post ) { |
464 |
387 if ( ! $this->check_read_permission( $post ) ) { |
465 foreach ( $query_result as $post ) { |
388 continue; |
466 if ( ! $this->check_read_permission( $post ) ) { |
389 } |
467 continue; |
390 |
468 } |
391 $data = $this->prepare_item_for_response( $post, $request ); |
469 |
392 $posts[] = $this->prepare_response_for_collection( $data ); |
470 $data = $this->prepare_item_for_response( $post, $request ); |
|
471 $posts[] = $this->prepare_response_for_collection( $data ); |
|
472 } |
393 } |
473 } |
394 |
474 |
395 // Reset filter. |
475 // Reset filter. |
396 if ( 'edit' === $request['context'] ) { |
476 if ( 'edit' === $request['context'] ) { |
397 remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); |
477 remove_filter( 'post_password_required', array( $this, 'check_password_required' ) ); |
398 } |
478 } |
399 |
479 |
400 $page = (int) $query_args['paged']; |
480 $page = isset( $query_args['paged'] ) ? (int) $query_args['paged'] : 0; |
401 $total_posts = $posts_query->found_posts; |
481 $total_posts = $posts_query->found_posts; |
402 |
482 |
403 if ( $total_posts < 1 && $page > 1 ) { |
483 if ( $total_posts < 1 && $page > 1 ) { |
404 // Out-of-bounds, run the query again without LIMIT for total count. |
484 // Out-of-bounds, run the query again without LIMIT for total count. |
405 unset( $query_args['paged'] ); |
485 unset( $query_args['paged'] ); |
417 __( 'The page number requested is larger than the number of pages available.' ), |
497 __( 'The page number requested is larger than the number of pages available.' ), |
418 array( 'status' => 400 ) |
498 array( 'status' => 400 ) |
419 ); |
499 ); |
420 } |
500 } |
421 |
501 |
422 $response = rest_ensure_response( $posts ); |
502 $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $posts ); |
423 |
503 |
424 $response->header( 'X-WP-Total', (int) $total_posts ); |
504 $response->header( 'X-WP-Total', (int) $total_posts ); |
425 $response->header( 'X-WP-TotalPages', (int) $max_pages ); |
505 $response->header( 'X-WP-TotalPages', (int) $max_pages ); |
426 |
506 |
427 $request_params = $request->get_query_params(); |
507 $request_params = $request->get_query_params(); |
495 __( 'Sorry, you are not allowed to edit this post.' ), |
575 __( 'Sorry, you are not allowed to edit this post.' ), |
496 array( 'status' => rest_authorization_required_code() ) |
576 array( 'status' => rest_authorization_required_code() ) |
497 ); |
577 ); |
498 } |
578 } |
499 |
579 |
500 if ( $post && ! empty( $request['password'] ) ) { |
580 if ( $post && ! empty( $request->get_query_params()['password'] ) ) { |
501 // Check post password, and return error if invalid. |
581 // Check post password, and return error if invalid. |
502 if ( ! hash_equals( $post->post_password, $request['password'] ) ) { |
582 if ( ! hash_equals( $post->post_password, $request->get_query_params()['password'] ) ) { |
503 return new WP_Error( |
583 return new WP_Error( |
504 'rest_post_incorrect_password', |
584 'rest_post_incorrect_password', |
505 __( 'Incorrect post password.' ), |
585 __( 'Incorrect post password.' ), |
506 array( 'status' => 403 ) |
586 array( 'status' => 403 ) |
507 ); |
587 ); |
1759 $post = $item; |
1841 $post = $item; |
1760 |
1842 |
1761 $GLOBALS['post'] = $post; |
1843 $GLOBALS['post'] = $post; |
1762 |
1844 |
1763 setup_postdata( $post ); |
1845 setup_postdata( $post ); |
|
1846 |
|
1847 // Don't prepare the response body for HEAD requests. |
|
1848 if ( $request->is_method( 'HEAD' ) ) { |
|
1849 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ |
|
1850 return apply_filters( "rest_prepare_{$this->post_type}", new WP_REST_Response( array() ), $post, $request ); |
|
1851 } |
1764 |
1852 |
1765 $fields = $this->get_fields_for_response( $request ); |
1853 $fields = $this->get_fields_for_response( $request ); |
1766 |
1854 |
1767 // Base fields for every post. |
1855 // Base fields for every post. |
1768 $data = array(); |
1856 $data = array(); |
1807 * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments |
1895 * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments |
1808 * above). In this case, shim the value based on the `post_modified` field |
1896 * above). In this case, shim the value based on the `post_modified` field |
1809 * with the site's timezone offset applied. |
1897 * with the site's timezone offset applied. |
1810 */ |
1898 */ |
1811 if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { |
1899 if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { |
1812 $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); |
1900 $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - (int) ( (float) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); |
1813 } else { |
1901 } else { |
1814 $post_modified_gmt = $post->post_modified_gmt; |
1902 $post_modified_gmt = $post->post_modified_gmt; |
1815 } |
1903 } |
1816 $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); |
1904 $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); |
1817 } |
1905 } |
1842 if ( rest_is_field_included( 'title.raw', $fields ) ) { |
1930 if ( rest_is_field_included( 'title.raw', $fields ) ) { |
1843 $data['title']['raw'] = $post->post_title; |
1931 $data['title']['raw'] = $post->post_title; |
1844 } |
1932 } |
1845 if ( rest_is_field_included( 'title.rendered', $fields ) ) { |
1933 if ( rest_is_field_included( 'title.rendered', $fields ) ) { |
1846 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); |
1934 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); |
|
1935 add_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); |
1847 |
1936 |
1848 $data['title']['rendered'] = get_the_title( $post->ID ); |
1937 $data['title']['rendered'] = get_the_title( $post->ID ); |
1849 |
1938 |
1850 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); |
1939 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); |
|
1940 remove_filter( 'private_title_format', array( $this, 'protected_title_format' ) ); |
1851 } |
1941 } |
1852 |
1942 |
1853 $has_password_filter = false; |
1943 $has_password_filter = false; |
1854 |
1944 |
1855 if ( $this->can_access_password_content( $post, $request ) ) { |
1945 if ( $this->can_access_password_content( $post, $request ) ) { |
2045 */ |
2135 */ |
2046 return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); |
2136 return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); |
2047 } |
2137 } |
2048 |
2138 |
2049 /** |
2139 /** |
2050 * Overwrites the default protected title format. |
2140 * Overwrites the default protected and private title format. |
2051 * |
2141 * |
2052 * By default, WordPress will show password protected posts with a title of |
2142 * By default, WordPress will show password protected or private posts with a title of |
2053 * "Protected: %s", as the REST API communicates the protected status of a post |
2143 * "Protected: %s" or "Private: %s", as the REST API communicates the status of a post |
2054 * in a machine readable format, we remove the "Protected: " prefix. |
2144 * in a machine-readable format, we remove the prefix. |
2055 * |
2145 * |
2056 * @since 4.7.0 |
2146 * @since 4.7.0 |
2057 * |
2147 * |
2058 * @return string Protected title format. |
2148 * @return string Title format. |
2059 */ |
2149 */ |
2060 public function protected_title_format() { |
2150 public function protected_title_format() { |
2061 return '%s'; |
2151 return '%s'; |
2062 } |
2152 } |
2063 |
2153 |
2882 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), |
2972 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), |
2883 'type' => 'integer', |
2973 'type' => 'integer', |
2884 ); |
2974 ); |
2885 } |
2975 } |
2886 |
2976 |
|
2977 $query_params['search_semantics'] = array( |
|
2978 'description' => __( 'How to interpret the search input.' ), |
|
2979 'type' => 'string', |
|
2980 'enum' => array( 'exact' ), |
|
2981 ); |
|
2982 |
2887 $query_params['offset'] = array( |
2983 $query_params['offset'] = array( |
2888 'description' => __( 'Offset the result set by a specific number of items.' ), |
2984 'description' => __( 'Offset the result set by a specific number of items.' ), |
2889 'type' => 'integer', |
2985 'type' => 'integer', |
2890 ); |
2986 ); |
2891 |
2987 |
2972 |
3068 |
2973 if ( 'post' === $this->post_type ) { |
3069 if ( 'post' === $this->post_type ) { |
2974 $query_params['sticky'] = array( |
3070 $query_params['sticky'] = array( |
2975 'description' => __( 'Limit result set to items that are sticky.' ), |
3071 'description' => __( 'Limit result set to items that are sticky.' ), |
2976 'type' => 'boolean', |
3072 'type' => 'boolean', |
|
3073 ); |
|
3074 |
|
3075 $query_params['ignore_sticky'] = array( |
|
3076 'description' => __( 'Whether to ignore sticky posts or not.' ), |
|
3077 'type' => 'boolean', |
|
3078 'default' => true, |
|
3079 ); |
|
3080 } |
|
3081 |
|
3082 if ( post_type_supports( $this->post_type, 'post-formats' ) ) { |
|
3083 $query_params['format'] = array( |
|
3084 'description' => __( 'Limit result set to items assigned one or more given formats.' ), |
|
3085 'type' => 'array', |
|
3086 'uniqueItems' => true, |
|
3087 'items' => array( |
|
3088 'enum' => array_values( get_post_format_slugs() ), |
|
3089 'type' => 'string', |
|
3090 ), |
2977 ); |
3091 ); |
2978 } |
3092 } |
2979 |
3093 |
2980 /** |
3094 /** |
2981 * Filters collection parameters for the posts controller. |
3095 * Filters collection parameters for the posts controller. |