wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
changeset 19 3d72ae0968f4
parent 18 be944660c56a
child 21 48c4eec2b7e6
equal deleted inserted replaced
18:be944660c56a 19:3d72ae0968f4
    31 	 *
    31 	 *
    32 	 * @param string $post_type Post type.
    32 	 * @param string $post_type Post type.
    33 	 */
    33 	 */
    34 	public function __construct( $post_type ) {
    34 	public function __construct( $post_type ) {
    35 		$this->post_type = $post_type;
    35 		$this->post_type = $post_type;
    36 		$this->namespace = 'wp/v2';
       
    37 		$obj             = get_post_type_object( $post_type );
    36 		$obj             = get_post_type_object( $post_type );
    38 		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
    37 		$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
       
    38 		$this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';
    39 	}
    39 	}
    40 
    40 
    41 	/**
    41 	/**
    42 	 * Registers the controllers routes.
    42 	 * Registers the controllers routes.
    43 	 *
    43 	 *
    66 		);
    66 		);
    67 
    67 
    68 		// Lists/updates a single template based on the given id.
    68 		// Lists/updates a single template based on the given id.
    69 		register_rest_route(
    69 		register_rest_route(
    70 			$this->namespace,
    70 			$this->namespace,
    71 			'/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
    71 			// The route.
       
    72 			sprintf(
       
    73 				'/%s/(?P<id>%s%s)',
       
    74 				$this->rest_base,
       
    75 				// Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
       
    76 				// Excludes invalid directory name characters: `/:<>*?"|`.
       
    77 				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
       
    78 				// Matches the template name.
       
    79 				'[\/\w-]+'
       
    80 			),
    72 			array(
    81 			array(
       
    82 				'args'   => array(
       
    83 					'id' => array(
       
    84 						'description'       => __( 'The id of a template' ),
       
    85 						'type'              => 'string',
       
    86 						'sanitize_callback' => array( $this, '_sanitize_template_id' ),
       
    87 					),
       
    88 				),
    73 				array(
    89 				array(
    74 					'methods'             => WP_REST_Server::READABLE,
    90 					'methods'             => WP_REST_Server::READABLE,
    75 					'callback'            => array( $this, 'get_item' ),
    91 					'callback'            => array( $this, 'get_item' ),
    76 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
    92 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
    77 					'args'                => array(
    93 					'args'                => array(
    78 						'id' => array(
    94 						'context' => $this->get_context_param( array( 'default' => 'view' ) ),
    79 							'description' => __( 'The id of a template' ),
       
    80 							'type'        => 'string',
       
    81 						),
       
    82 					),
    95 					),
    83 				),
    96 				),
    84 				array(
    97 				array(
    85 					'methods'             => WP_REST_Server::EDITABLE,
    98 					'methods'             => WP_REST_Server::EDITABLE,
    86 					'callback'            => array( $this, 'update_item' ),
    99 					'callback'            => array( $this, 'update_item' ),
   127 
   140 
   128 		return true;
   141 		return true;
   129 	}
   142 	}
   130 
   143 
   131 	/**
   144 	/**
       
   145 	 * Requesting this endpoint for a template like 'twentytwentytwo//home'
       
   146 	 * requires using a path like /wp/v2/templates/twentytwentytwo//home. There
       
   147 	 * are special cases when WordPress routing corrects the name to contain
       
   148 	 * only a single slash like 'twentytwentytwo/home'.
       
   149 	 *
       
   150 	 * This method doubles the last slash if it's not already doubled. It relies
       
   151 	 * on the template ID format {theme_name}//{template_slug} and the fact that
       
   152 	 * slugs cannot contain slashes.
       
   153 	 *
       
   154 	 * @since 5.9.0
       
   155 	 * @see https://core.trac.wordpress.org/ticket/54507
       
   156 	 *
       
   157 	 * @param string $id Template ID.
       
   158 	 * @return string Sanitized template ID.
       
   159 	 */
       
   160 	public function _sanitize_template_id( $id ) {
       
   161 		$id = urldecode( $id );
       
   162 
       
   163 		$last_slash_pos = strrpos( $id, '/' );
       
   164 		if ( false === $last_slash_pos ) {
       
   165 			return $id;
       
   166 		}
       
   167 
       
   168 		$is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/';
       
   169 		if ( $is_double_slashed ) {
       
   170 			return $id;
       
   171 		}
       
   172 		return (
       
   173 			substr( $id, 0, $last_slash_pos )
       
   174 			. '/'
       
   175 			. substr( $id, $last_slash_pos )
       
   176 		);
       
   177 	}
       
   178 
       
   179 	/**
   132 	 * Checks if a given request has access to read templates.
   180 	 * Checks if a given request has access to read templates.
   133 	 *
   181 	 *
   134 	 * @since 5.8.0
   182 	 * @since 5.8.0
   135 	 *
   183 	 *
   136 	 * @param WP_REST_Request $request Full details about the request.
   184 	 * @param WP_REST_Request $request Full details about the request.
   154 			$query['wp_id'] = $request['wp_id'];
   202 			$query['wp_id'] = $request['wp_id'];
   155 		}
   203 		}
   156 		if ( isset( $request['area'] ) ) {
   204 		if ( isset( $request['area'] ) ) {
   157 			$query['area'] = $request['area'];
   205 			$query['area'] = $request['area'];
   158 		}
   206 		}
       
   207 		if ( isset( $request['post_type'] ) ) {
       
   208 			$query['post_type'] = $request['post_type'];
       
   209 		}
   159 
   210 
   160 		$templates = array();
   211 		$templates = array();
   161 		foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
   212 		foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
   162 			$data        = $this->prepare_item_for_response( $template, $request );
   213 			$data        = $this->prepare_item_for_response( $template, $request );
   163 			$templates[] = $this->prepare_response_for_collection( $data );
   214 			$templates[] = $this->prepare_response_for_collection( $data );
   185 	 *
   236 	 *
   186 	 * @param WP_REST_Request $request The request instance.
   237 	 * @param WP_REST_Request $request The request instance.
   187 	 * @return WP_REST_Response|WP_Error
   238 	 * @return WP_REST_Response|WP_Error
   188 	 */
   239 	 */
   189 	public function get_item( $request ) {
   240 	public function get_item( $request ) {
   190 		$template = get_block_template( $request['id'], $this->post_type );
   241 		if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
       
   242 			$template = get_block_file_template( $request['id'], $this->post_type );
       
   243 		} else {
       
   244 			$template = get_block_template( $request['id'], $this->post_type );
       
   245 		}
   191 
   246 
   192 		if ( ! $template ) {
   247 		if ( ! $template ) {
   193 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
   248 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
   194 		}
   249 		}
   195 
   250 
   220 		$template = get_block_template( $request['id'], $this->post_type );
   275 		$template = get_block_template( $request['id'], $this->post_type );
   221 		if ( ! $template ) {
   276 		if ( ! $template ) {
   222 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
   277 			return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
   223 		}
   278 		}
   224 
   279 
       
   280 		$post_before = get_post( $template->wp_id );
       
   281 
       
   282 		if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
       
   283 			wp_delete_post( $template->wp_id, true );
       
   284 			$request->set_param( 'context', 'edit' );
       
   285 
       
   286 			$template = get_block_template( $request['id'], $this->post_type );
       
   287 			$response = $this->prepare_item_for_response( $template, $request );
       
   288 
       
   289 			return rest_ensure_response( $response );
       
   290 		}
       
   291 
   225 		$changes = $this->prepare_item_for_database( $request );
   292 		$changes = $this->prepare_item_for_database( $request );
   226 
   293 
       
   294 		if ( is_wp_error( $changes ) ) {
       
   295 			return $changes;
       
   296 		}
       
   297 
   227 		if ( 'custom' === $template->source ) {
   298 		if ( 'custom' === $template->source ) {
   228 			$result = wp_update_post( wp_slash( (array) $changes ), true );
   299 			$update = true;
       
   300 			$result = wp_update_post( wp_slash( (array) $changes ), false );
   229 		} else {
   301 		} else {
   230 			$result = wp_insert_post( wp_slash( (array) $changes ), true );
   302 			$update      = false;
   231 		}
   303 			$post_before = null;
       
   304 			$result      = wp_insert_post( wp_slash( (array) $changes ), false );
       
   305 		}
       
   306 
   232 		if ( is_wp_error( $result ) ) {
   307 		if ( is_wp_error( $result ) ) {
       
   308 			if ( 'db_update_error' === $result->get_error_code() ) {
       
   309 				$result->add_data( array( 'status' => 500 ) );
       
   310 			} else {
       
   311 				$result->add_data( array( 'status' => 400 ) );
       
   312 			}
   233 			return $result;
   313 			return $result;
   234 		}
   314 		}
   235 
   315 
   236 		$template      = get_block_template( $request['id'], $this->post_type );
   316 		$template      = get_block_template( $request['id'], $this->post_type );
   237 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
   317 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
   238 		if ( is_wp_error( $fields_update ) ) {
   318 		if ( is_wp_error( $fields_update ) ) {
   239 			return $fields_update;
   319 			return $fields_update;
   240 		}
   320 		}
   241 
   321 
   242 		return $this->prepare_item_for_response(
   322 		$request->set_param( 'context', 'edit' );
   243 			get_block_template( $request['id'], $this->post_type ),
   323 
   244 			$request
   324 		$post = get_post( $template->wp_id );
   245 		);
   325 		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
       
   326 		do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );
       
   327 
       
   328 		wp_after_insert_post( $post, $update, $post_before );
       
   329 
       
   330 		$response = $this->prepare_item_for_response( $template, $request );
       
   331 
       
   332 		return rest_ensure_response( $response );
   246 	}
   333 	}
   247 
   334 
   248 	/**
   335 	/**
   249 	 * Checks if a given request has access to create a template.
   336 	 * Checks if a given request has access to create a template.
   250 	 *
   337 	 *
   264 	 *
   351 	 *
   265 	 * @param WP_REST_Request $request Full details about the request.
   352 	 * @param WP_REST_Request $request Full details about the request.
   266 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
   353 	 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
   267 	 */
   354 	 */
   268 	public function create_item( $request ) {
   355 	public function create_item( $request ) {
   269 		$changes            = $this->prepare_item_for_database( $request );
   356 		$prepared_post = $this->prepare_item_for_database( $request );
   270 		$changes->post_name = $request['slug'];
   357 
   271 		$result             = wp_insert_post( wp_slash( (array) $changes ), true );
   358 		if ( is_wp_error( $prepared_post ) ) {
   272 		if ( is_wp_error( $result ) ) {
   359 			return $prepared_post;
   273 			return $result;
   360 		}
   274 		}
   361 
   275 		$posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
   362 		$prepared_post->post_name = $request['slug'];
       
   363 		$post_id                  = wp_insert_post( wp_slash( (array) $prepared_post ), true );
       
   364 		if ( is_wp_error( $post_id ) ) {
       
   365 			if ( 'db_insert_error' === $post_id->get_error_code() ) {
       
   366 				$post_id->add_data( array( 'status' => 500 ) );
       
   367 			} else {
       
   368 				$post_id->add_data( array( 'status' => 400 ) );
       
   369 			}
       
   370 
       
   371 			return $post_id;
       
   372 		}
       
   373 		$posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type );
   276 		if ( ! count( $posts ) ) {
   374 		if ( ! count( $posts ) ) {
   277 			return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
   375 			return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) );
   278 		}
   376 		}
   279 		$id            = $posts[0]->id;
   377 		$id            = $posts[0]->id;
       
   378 		$post          = get_post( $post_id );
   280 		$template      = get_block_template( $id, $this->post_type );
   379 		$template      = get_block_template( $id, $this->post_type );
   281 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
   380 		$fields_update = $this->update_additional_fields_for_object( $template, $request );
   282 		if ( is_wp_error( $fields_update ) ) {
   381 		if ( is_wp_error( $fields_update ) ) {
   283 			return $fields_update;
   382 			return $fields_update;
   284 		}
   383 		}
   285 
   384 
   286 		return $this->prepare_item_for_response(
   385 		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
   287 			get_block_template( $id, $this->post_type ),
   386 		do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );
   288 			$request
   387 
   289 		);
   388 		wp_after_insert_post( $post, false, null );
       
   389 
       
   390 		$response = $this->prepare_item_for_response( $template, $request );
       
   391 		$response = rest_ensure_response( $response );
       
   392 
       
   393 		$response->set_status( 201 );
       
   394 		$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) );
       
   395 
       
   396 		return $response;
   290 	}
   397 	}
   291 
   398 
   292 	/**
   399 	/**
   293 	 * Checks if a given request has access to delete a single template.
   400 	 * Checks if a given request has access to delete a single template.
   294 	 *
   401 	 *
   319 		}
   426 		}
   320 
   427 
   321 		$id    = $template->wp_id;
   428 		$id    = $template->wp_id;
   322 		$force = (bool) $request['force'];
   429 		$force = (bool) $request['force'];
   323 
   430 
       
   431 		$request->set_param( 'context', 'edit' );
       
   432 
   324 		// If we're forcing, then delete permanently.
   433 		// If we're forcing, then delete permanently.
   325 		if ( $force ) {
   434 		if ( $force ) {
   326 			$previous = $this->prepare_item_for_response( $template, $request );
   435 			$previous = $this->prepare_item_for_response( $template, $request );
   327 			wp_delete_post( $id, true );
   436 			$result   = wp_delete_post( $id, true );
   328 			$response = new WP_REST_Response();
   437 			$response = new WP_REST_Response();
   329 			$response->set_data(
   438 			$response->set_data(
   330 				array(
   439 				array(
   331 					'deleted'  => true,
   440 					'deleted'  => true,
   332 					'previous' => $previous->get_data(),
   441 					'previous' => $previous->get_data(),
   333 				)
   442 				)
   334 			);
   443 			);
   335 
   444 		} else {
   336 			return $response;
   445 			// Otherwise, only trash if we haven't already.
   337 		}
   446 			if ( 'trash' === $template->status ) {
   338 
   447 				return new WP_Error(
   339 		// Otherwise, only trash if we haven't already.
   448 					'rest_template_already_trashed',
   340 		if ( 'trash' === $template->status ) {
   449 					__( 'The template has already been deleted.' ),
       
   450 					array( 'status' => 410 )
       
   451 				);
       
   452 			}
       
   453 
       
   454 			// (Note that internally this falls through to `wp_delete_post()`
       
   455 			// if the Trash is disabled.)
       
   456 			$result           = wp_trash_post( $id );
       
   457 			$template->status = 'trash';
       
   458 			$response         = $this->prepare_item_for_response( $template, $request );
       
   459 		}
       
   460 
       
   461 		if ( ! $result ) {
   341 			return new WP_Error(
   462 			return new WP_Error(
   342 				'rest_template_already_trashed',
   463 				'rest_cannot_delete',
   343 				__( 'The template has already been deleted.' ),
   464 				__( 'The template cannot be deleted.' ),
   344 				array( 'status' => 410 )
   465 				array( 'status' => 500 )
   345 			);
   466 			);
   346 		}
   467 		}
   347 
   468 
   348 		wp_trash_post( $id );
   469 		return $response;
   349 		$template->status = 'trash';
       
   350 		return $this->prepare_item_for_response( $template, $request );
       
   351 	}
   470 	}
   352 
   471 
   353 	/**
   472 	/**
   354 	 * Prepares a single template for create or update.
   473 	 * Prepares a single template for create or update.
   355 	 *
   474 	 *
   372 			$changes->post_type   = $this->post_type;
   491 			$changes->post_type   = $this->post_type;
   373 			$changes->post_status = 'publish';
   492 			$changes->post_status = 'publish';
   374 			$changes->tax_input   = array(
   493 			$changes->tax_input   = array(
   375 				'wp_theme' => $template->theme,
   494 				'wp_theme' => $template->theme,
   376 			);
   495 			);
       
   496 			$changes->meta_input  = array(
       
   497 				'origin' => $template->source,
       
   498 			);
   377 		} else {
   499 		} else {
   378 			$changes->post_name   = $template->slug;
   500 			$changes->post_name   = $template->slug;
   379 			$changes->ID          = $template->wp_id;
   501 			$changes->ID          = $template->wp_id;
   380 			$changes->post_status = 'publish';
   502 			$changes->post_status = 'publish';
   381 		}
   503 		}
   382 		if ( isset( $request['content'] ) ) {
   504 		if ( isset( $request['content'] ) ) {
   383 			$changes->post_content = $request['content'];
   505 			if ( is_string( $request['content'] ) ) {
       
   506 				$changes->post_content = $request['content'];
       
   507 			} elseif ( isset( $request['content']['raw'] ) ) {
       
   508 				$changes->post_content = $request['content']['raw'];
       
   509 			}
   384 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   510 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   385 			$changes->post_content = $template->content;
   511 			$changes->post_content = $template->content;
   386 		}
   512 		}
   387 		if ( isset( $request['title'] ) ) {
   513 		if ( isset( $request['title'] ) ) {
   388 			$changes->post_title = $request['title'];
   514 			if ( is_string( $request['title'] ) ) {
       
   515 				$changes->post_title = $request['title'];
       
   516 			} elseif ( ! empty( $request['title']['raw'] ) ) {
       
   517 				$changes->post_title = $request['title']['raw'];
       
   518 			}
   389 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   519 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   390 			$changes->post_title = $template->title;
   520 			$changes->post_title = $template->title;
   391 		}
   521 		}
   392 		if ( isset( $request['description'] ) ) {
   522 		if ( isset( $request['description'] ) ) {
   393 			$changes->post_excerpt = $request['description'];
   523 			$changes->post_excerpt = $request['description'];
   394 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   524 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   395 			$changes->post_excerpt = $template->description;
   525 			$changes->post_excerpt = $template->description;
   396 		}
   526 		}
   397 
   527 
       
   528 		if ( 'wp_template_part' === $this->post_type ) {
       
   529 			if ( isset( $request['area'] ) ) {
       
   530 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
       
   531 			} elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
       
   532 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
       
   533 			} elseif ( ! $template->area ) {
       
   534 				$changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
       
   535 			}
       
   536 		}
       
   537 
       
   538 		if ( ! empty( $request['author'] ) ) {
       
   539 			$post_author = (int) $request['author'];
       
   540 
       
   541 			if ( get_current_user_id() !== $post_author ) {
       
   542 				$user_obj = get_userdata( $post_author );
       
   543 
       
   544 				if ( ! $user_obj ) {
       
   545 					return new WP_Error(
       
   546 						'rest_invalid_author',
       
   547 						__( 'Invalid author ID.' ),
       
   548 						array( 'status' => 400 )
       
   549 					);
       
   550 				}
       
   551 			}
       
   552 
       
   553 			$changes->post_author = $post_author;
       
   554 		}
       
   555 
   398 		return $changes;
   556 		return $changes;
   399 	}
   557 	}
   400 
   558 
   401 	/**
   559 	/**
   402 	 * Prepare a single template output for response
   560 	 * Prepare a single template output for response
   403 	 *
   561 	 *
   404 	 * @since 5.8.0
   562 	 * @since 5.8.0
   405 	 *
   563 	 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
   406 	 * @param WP_Block_Template $template Template instance.
   564 	 *
       
   565 	 * @param WP_Block_Template $item    Template instance.
   407 	 * @param WP_REST_Request   $request Request object.
   566 	 * @param WP_REST_Request   $request Request object.
   408 	 * @return WP_REST_Response $data
   567 	 * @return WP_REST_Response Response object.
   409 	 */
   568 	 */
   410 	public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
   569 	public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
   411 		$result = array(
   570 		// Restores the more descriptive, specific name for use within this method.
   412 			'id'             => $template->id,
   571 		$template = $item;
   413 			'theme'          => $template->theme,
   572 
   414 			'content'        => array( 'raw' => $template->content ),
   573 		$fields = $this->get_fields_for_response( $request );
   415 			'slug'           => $template->slug,
   574 
   416 			'source'         => $template->source,
   575 		// Base fields for every template.
   417 			'type'           => $template->type,
   576 		$data = array();
   418 			'description'    => $template->description,
   577 
   419 			'title'          => array(
   578 		if ( rest_is_field_included( 'id', $fields ) ) {
   420 				'raw'      => $template->title,
   579 			$data['id'] = $template->id;
   421 				'rendered' => $template->title,
   580 		}
   422 			),
   581 
   423 			'status'         => $template->status,
   582 		if ( rest_is_field_included( 'theme', $fields ) ) {
   424 			'wp_id'          => $template->wp_id,
   583 			$data['theme'] = $template->theme;
   425 			'has_theme_file' => $template->has_theme_file,
   584 		}
   426 		);
   585 
   427 
   586 		if ( rest_is_field_included( 'content', $fields ) ) {
   428 		if ( 'wp_template_part' === $template->type ) {
   587 			$data['content'] = array();
   429 			$result['area'] = $template->area;
   588 		}
   430 		}
   589 		if ( rest_is_field_included( 'content.raw', $fields ) ) {
   431 
   590 			$data['content']['raw'] = $template->content;
   432 		$result = $this->add_additional_fields_to_object( $result, $request );
   591 		}
   433 
   592 
   434 		$response = rest_ensure_response( $result );
   593 		if ( rest_is_field_included( 'content.block_version', $fields ) ) {
   435 		$links    = $this->prepare_links( $template->id );
   594 			$data['content']['block_version'] = block_version( $template->content );
       
   595 		}
       
   596 
       
   597 		if ( rest_is_field_included( 'slug', $fields ) ) {
       
   598 			$data['slug'] = $template->slug;
       
   599 		}
       
   600 
       
   601 		if ( rest_is_field_included( 'source', $fields ) ) {
       
   602 			$data['source'] = $template->source;
       
   603 		}
       
   604 
       
   605 		if ( rest_is_field_included( 'origin', $fields ) ) {
       
   606 			$data['origin'] = $template->origin;
       
   607 		}
       
   608 
       
   609 		if ( rest_is_field_included( 'type', $fields ) ) {
       
   610 			$data['type'] = $template->type;
       
   611 		}
       
   612 
       
   613 		if ( rest_is_field_included( 'description', $fields ) ) {
       
   614 			$data['description'] = $template->description;
       
   615 		}
       
   616 
       
   617 		if ( rest_is_field_included( 'title', $fields ) ) {
       
   618 			$data['title'] = array();
       
   619 		}
       
   620 
       
   621 		if ( rest_is_field_included( 'title.raw', $fields ) ) {
       
   622 			$data['title']['raw'] = $template->title;
       
   623 		}
       
   624 
       
   625 		if ( rest_is_field_included( 'title.rendered', $fields ) ) {
       
   626 			if ( $template->wp_id ) {
       
   627 				/** This filter is documented in wp-includes/post-template.php */
       
   628 				$data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id );
       
   629 			} else {
       
   630 				$data['title']['rendered'] = $template->title;
       
   631 			}
       
   632 		}
       
   633 
       
   634 		if ( rest_is_field_included( 'status', $fields ) ) {
       
   635 			$data['status'] = $template->status;
       
   636 		}
       
   637 
       
   638 		if ( rest_is_field_included( 'wp_id', $fields ) ) {
       
   639 			$data['wp_id'] = (int) $template->wp_id;
       
   640 		}
       
   641 
       
   642 		if ( rest_is_field_included( 'has_theme_file', $fields ) ) {
       
   643 			$data['has_theme_file'] = (bool) $template->has_theme_file;
       
   644 		}
       
   645 
       
   646 		if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) {
       
   647 			$data['is_custom'] = $template->is_custom;
       
   648 		}
       
   649 
       
   650 		if ( rest_is_field_included( 'author', $fields ) ) {
       
   651 			$data['author'] = (int) $template->author;
       
   652 		}
       
   653 
       
   654 		if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
       
   655 			$data['area'] = $template->area;
       
   656 		}
       
   657 
       
   658 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
       
   659 		$data    = $this->add_additional_fields_to_object( $data, $request );
       
   660 		$data    = $this->filter_response_by_context( $data, $context );
       
   661 
       
   662 		// Wrap the data in a response object.
       
   663 		$response = rest_ensure_response( $data );
       
   664 
       
   665 		$links = $this->prepare_links( $template->id );
   436 		$response->add_links( $links );
   666 		$response->add_links( $links );
   437 		if ( ! empty( $links['self']['href'] ) ) {
   667 		if ( ! empty( $links['self']['href'] ) ) {
   438 			$actions = $this->get_available_actions();
   668 			$actions = $this->get_available_actions();
   439 			$self    = $links['self']['href'];
   669 			$self    = $links['self']['href'];
   440 			foreach ( $actions as $rel ) {
   670 			foreach ( $actions as $rel ) {
   475 	/**
   705 	/**
   476 	 * Get the link relations available for the post and current user.
   706 	 * Get the link relations available for the post and current user.
   477 	 *
   707 	 *
   478 	 * @since 5.8.0
   708 	 * @since 5.8.0
   479 	 *
   709 	 *
   480 	 * @return array List of link relations.
   710 	 * @return string[] List of link relations.
   481 	 */
   711 	 */
   482 	protected function get_available_actions() {
   712 	protected function get_available_actions() {
   483 		$rels = array();
   713 		$rels = array();
   484 
   714 
   485 		$post_type = get_post_type_object( $this->post_type );
   715 		$post_type = get_post_type_object( $this->post_type );
   497 
   727 
   498 	/**
   728 	/**
   499 	 * Retrieves the query params for the posts collection.
   729 	 * Retrieves the query params for the posts collection.
   500 	 *
   730 	 *
   501 	 * @since 5.8.0
   731 	 * @since 5.8.0
       
   732 	 * @since 5.9.0 Added `'area'` and `'post_type'`.
   502 	 *
   733 	 *
   503 	 * @return array Collection parameters.
   734 	 * @return array Collection parameters.
   504 	 */
   735 	 */
   505 	public function get_collection_params() {
   736 	public function get_collection_params() {
   506 		return array(
   737 		return array(
   507 			'context' => $this->get_context_param(),
   738 			'context'   => $this->get_context_param( array( 'default' => 'view' ) ),
   508 			'wp_id'   => array(
   739 			'wp_id'     => array(
   509 				'description' => __( 'Limit to the specified post id.' ),
   740 				'description' => __( 'Limit to the specified post id.' ),
   510 				'type'        => 'integer',
   741 				'type'        => 'integer',
   511 			),
   742 			),
       
   743 			'area'      => array(
       
   744 				'description' => __( 'Limit to the specified template part area.' ),
       
   745 				'type'        => 'string',
       
   746 			),
       
   747 			'post_type' => array(
       
   748 				'description' => __( 'Post type to get the templates for.' ),
       
   749 				'type'        => 'string',
       
   750 			),
   512 		);
   751 		);
   513 	}
   752 	}
   514 
   753 
   515 	/**
   754 	/**
   516 	 * Retrieves the block type' schema, conforming to JSON Schema.
   755 	 * Retrieves the block type' schema, conforming to JSON Schema.
   517 	 *
   756 	 *
   518 	 * @since 5.8.0
   757 	 * @since 5.8.0
       
   758 	 * @since 5.9.0 Added `'area'`.
   519 	 *
   759 	 *
   520 	 * @return array Item schema data.
   760 	 * @return array Item schema data.
   521 	 */
   761 	 */
   522 	public function get_item_schema() {
   762 	public function get_item_schema() {
   523 		if ( $this->schema ) {
   763 		if ( $this->schema ) {
   539 					'description' => __( 'Unique slug identifying the template.' ),
   779 					'description' => __( 'Unique slug identifying the template.' ),
   540 					'type'        => 'string',
   780 					'type'        => 'string',
   541 					'context'     => array( 'embed', 'view', 'edit' ),
   781 					'context'     => array( 'embed', 'view', 'edit' ),
   542 					'required'    => true,
   782 					'required'    => true,
   543 					'minLength'   => 1,
   783 					'minLength'   => 1,
   544 					'pattern'     => '[a-zA-Z_\-]+',
   784 					'pattern'     => '[a-zA-Z0-9_\-]+',
   545 				),
   785 				),
   546 				'theme'          => array(
   786 				'theme'          => array(
   547 					'description' => __( 'Theme identifier for the template.' ),
   787 					'description' => __( 'Theme identifier for the template.' ),
   548 					'type'        => 'string',
   788 					'type'        => 'string',
   549 					'context'     => array( 'embed', 'view', 'edit' ),
   789 					'context'     => array( 'embed', 'view', 'edit' ),
   550 				),
   790 				),
       
   791 				'type'           => array(
       
   792 					'description' => __( 'Type of template.' ),
       
   793 					'type'        => 'string',
       
   794 					'context'     => array( 'embed', 'view', 'edit' ),
       
   795 				),
   551 				'source'         => array(
   796 				'source'         => array(
   552 					'description' => __( 'Source of template' ),
   797 					'description' => __( 'Source of template' ),
       
   798 					'type'        => 'string',
       
   799 					'context'     => array( 'embed', 'view', 'edit' ),
       
   800 					'readonly'    => true,
       
   801 				),
       
   802 				'origin'         => array(
       
   803 					'description' => __( 'Source of a customized template' ),
   553 					'type'        => 'string',
   804 					'type'        => 'string',
   554 					'context'     => array( 'embed', 'view', 'edit' ),
   805 					'context'     => array( 'embed', 'view', 'edit' ),
   555 					'readonly'    => true,
   806 					'readonly'    => true,
   556 				),
   807 				),
   557 				'content'        => array(
   808 				'content'        => array(
   558 					'description' => __( 'Content of template.' ),
   809 					'description' => __( 'Content of template.' ),
   559 					'type'        => array( 'object', 'string' ),
   810 					'type'        => array( 'object', 'string' ),
   560 					'default'     => '',
   811 					'default'     => '',
   561 					'context'     => array( 'embed', 'view', 'edit' ),
   812 					'context'     => array( 'embed', 'view', 'edit' ),
       
   813 					'properties'  => array(
       
   814 						'raw'           => array(
       
   815 							'description' => __( 'Content for the template, as it exists in the database.' ),
       
   816 							'type'        => 'string',
       
   817 							'context'     => array( 'view', 'edit' ),
       
   818 						),
       
   819 						'block_version' => array(
       
   820 							'description' => __( 'Version of the content block format used by the template.' ),
       
   821 							'type'        => 'integer',
       
   822 							'context'     => array( 'edit' ),
       
   823 							'readonly'    => true,
       
   824 						),
       
   825 					),
   562 				),
   826 				),
   563 				'title'          => array(
   827 				'title'          => array(
   564 					'description' => __( 'Title of template.' ),
   828 					'description' => __( 'Title of template.' ),
   565 					'type'        => array( 'object', 'string' ),
   829 					'type'        => array( 'object', 'string' ),
   566 					'default'     => '',
   830 					'default'     => '',
   567 					'context'     => array( 'embed', 'view', 'edit' ),
   831 					'context'     => array( 'embed', 'view', 'edit' ),
       
   832 					'properties'  => array(
       
   833 						'raw'      => array(
       
   834 							'description' => __( 'Title for the template, as it exists in the database.' ),
       
   835 							'type'        => 'string',
       
   836 							'context'     => array( 'view', 'edit', 'embed' ),
       
   837 						),
       
   838 						'rendered' => array(
       
   839 							'description' => __( 'HTML title for the template, transformed for display.' ),
       
   840 							'type'        => 'string',
       
   841 							'context'     => array( 'view', 'edit', 'embed' ),
       
   842 							'readonly'    => true,
       
   843 						),
       
   844 					),
   568 				),
   845 				),
   569 				'description'    => array(
   846 				'description'    => array(
   570 					'description' => __( 'Description of template.' ),
   847 					'description' => __( 'Description of template.' ),
   571 					'type'        => 'string',
   848 					'type'        => 'string',
   572 					'default'     => '',
   849 					'default'     => '',
   573 					'context'     => array( 'embed', 'view', 'edit' ),
   850 					'context'     => array( 'embed', 'view', 'edit' ),
   574 				),
   851 				),
   575 				'status'         => array(
   852 				'status'         => array(
   576 					'description' => __( 'Status of template.' ),
   853 					'description' => __( 'Status of template.' ),
   577 					'type'        => 'string',
   854 					'type'        => 'string',
       
   855 					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
   578 					'default'     => 'publish',
   856 					'default'     => 'publish',
   579 					'context'     => array( 'embed', 'view', 'edit' ),
   857 					'context'     => array( 'embed', 'view', 'edit' ),
   580 				),
   858 				),
   581 				'wp_id'          => array(
   859 				'wp_id'          => array(
   582 					'description' => __( 'Post ID.' ),
   860 					'description' => __( 'Post ID.' ),
   588 					'description' => __( 'Theme file exists.' ),
   866 					'description' => __( 'Theme file exists.' ),
   589 					'type'        => 'bool',
   867 					'type'        => 'bool',
   590 					'context'     => array( 'embed', 'view', 'edit' ),
   868 					'context'     => array( 'embed', 'view', 'edit' ),
   591 					'readonly'    => true,
   869 					'readonly'    => true,
   592 				),
   870 				),
       
   871 				'author'         => array(
       
   872 					'description' => __( 'The ID for the author of the template.' ),
       
   873 					'type'        => 'integer',
       
   874 					'context'     => array( 'view', 'edit', 'embed' ),
       
   875 				),
   593 			),
   876 			),
   594 		);
   877 		);
   595 
   878 
       
   879 		if ( 'wp_template' === $this->post_type ) {
       
   880 			$schema['properties']['is_custom'] = array(
       
   881 				'description' => __( 'Whether a template is a custom template.' ),
       
   882 				'type'        => 'bool',
       
   883 				'context'     => array( 'embed', 'view', 'edit' ),
       
   884 				'readonly'    => true,
       
   885 			);
       
   886 		}
       
   887 
       
   888 		if ( 'wp_template_part' === $this->post_type ) {
       
   889 			$schema['properties']['area'] = array(
       
   890 				'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ),
       
   891 				'type'        => 'string',
       
   892 				'context'     => array( 'embed', 'view', 'edit' ),
       
   893 			);
       
   894 		}
       
   895 
   596 		$this->schema = $schema;
   896 		$this->schema = $schema;
   597 
   897 
   598 		return $this->add_additional_fields_schema( $this->schema );
   898 		return $this->add_additional_fields_schema( $this->schema );
   599 	}
   899 	}
   600 }
   900 }