wp/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
changeset 22 8c2e4d02f4ef
parent 21 48c4eec2b7e6
equal deleted inserted replaced
21:48c4eec2b7e6 22:8c2e4d02f4ef
   181 	 *
   181 	 *
   182 	 * @since 5.7.1
   182 	 * @since 5.7.1
   183 	 *
   183 	 *
   184 	 * @param bool    $required Whether the post requires a password check.
   184 	 * @param bool    $required Whether the post requires a password check.
   185 	 * @param WP_Post $post     The post been password checked.
   185 	 * @param WP_Post $post     The post been password checked.
   186 	 * @return bool Result of password check taking in to account REST API considerations.
   186 	 * @return bool Result of password check taking into account REST API considerations.
   187 	 */
   187 	 */
   188 	public function check_password_required( $required, $post ) {
   188 	public function check_password_required( $required, $post ) {
   189 		if ( ! $required ) {
   189 		if ( ! $required ) {
   190 			return $required;
   190 			return $required;
   191 		}
   191 		}
   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 				);
  1587 
  1667 
  1588 			if ( is_wp_error( $result ) ) {
  1668 			if ( is_wp_error( $result ) ) {
  1589 				return $result;
  1669 				return $result;
  1590 			}
  1670 			}
  1591 		}
  1671 		}
       
  1672 
       
  1673 		return null;
  1592 	}
  1674 	}
  1593 
  1675 
  1594 	/**
  1676 	/**
  1595 	 * Checks whether current user can assign all terms sent with the current request.
  1677 	 * Checks whether current user can assign all terms sent with the current request.
  1596 	 *
  1678 	 *
  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.