wp/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
    40 
    40 
    41 	/**
    41 	/**
    42 	 * Registers the controllers routes.
    42 	 * Registers the controllers routes.
    43 	 *
    43 	 *
    44 	 * @since 5.8.0
    44 	 * @since 5.8.0
       
    45 	 * @since 6.1.0 Endpoint for fallback template content.
    45 	 */
    46 	 */
    46 	public function register_routes() {
    47 	public function register_routes() {
    47 		// Lists all templates.
    48 		// Lists all templates.
    48 		register_rest_route(
    49 		register_rest_route(
    49 			$this->namespace,
    50 			$this->namespace,
    63 				),
    64 				),
    64 				'schema' => array( $this, 'get_public_item_schema' ),
    65 				'schema' => array( $this, 'get_public_item_schema' ),
    65 			)
    66 			)
    66 		);
    67 		);
    67 
    68 
       
    69 		// Get fallback template content.
       
    70 		register_rest_route(
       
    71 			$this->namespace,
       
    72 			'/' . $this->rest_base . '/lookup',
       
    73 			array(
       
    74 				array(
       
    75 					'methods'             => WP_REST_Server::READABLE,
       
    76 					'callback'            => array( $this, 'get_template_fallback' ),
       
    77 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
       
    78 					'args'                => array(
       
    79 						'slug'            => array(
       
    80 							'description' => __( 'The slug of the template to get the fallback for' ),
       
    81 							'type'        => 'string',
       
    82 							'required'    => true,
       
    83 						),
       
    84 						'is_custom'       => array(
       
    85 							'description' => __( 'Indicates if a template is custom or part of the template hierarchy' ),
       
    86 							'type'        => 'boolean',
       
    87 						),
       
    88 						'template_prefix' => array(
       
    89 							'description' => __( 'The template prefix for the created template. This is used to extract the main template type, e.g. in `taxonomy-books` extracts the `taxonomy`' ),
       
    90 							'type'        => 'string',
       
    91 						),
       
    92 					),
       
    93 				),
       
    94 			)
       
    95 		);
       
    96 
    68 		// Lists/updates a single template based on the given id.
    97 		// Lists/updates a single template based on the given id.
    69 		register_rest_route(
    98 		register_rest_route(
    70 			$this->namespace,
    99 			$this->namespace,
    71 			// The route.
   100 			// The route.
    72 			sprintf(
   101 			sprintf(
    73 				'/%s/(?P<id>%s%s)',
   102 				'/%s/(?P<id>%s%s)',
    74 				$this->rest_base,
   103 				$this->rest_base,
    75 				// Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
   104 				/*
    76 				// Excludes invalid directory name characters: `/:<>*?"|`.
   105 				 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
       
   106 				 * Excludes invalid directory name characters: `/:<>*?"|`.
       
   107 				 */
    77 				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
   108 				'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
    78 				// Matches the template name.
   109 				// Matches the template name.
    79 				'[\/\w-]+'
   110 				'[\/\w%-]+'
    80 			),
   111 			),
    81 			array(
   112 			array(
    82 				'args'   => array(
   113 				'args'   => array(
    83 					'id' => array(
   114 					'id' => array(
    84 						'description'       => __( 'The id of a template' ),
   115 						'description'       => __( 'The id of a template' ),
   116 			)
   147 			)
   117 		);
   148 		);
   118 	}
   149 	}
   119 
   150 
   120 	/**
   151 	/**
       
   152 	 * Returns the fallback template for the given slug.
       
   153 	 *
       
   154 	 * @since 6.1.0
       
   155 	 * @since 6.3.0 Ignore empty templates.
       
   156 	 *
       
   157 	 * @param WP_REST_Request $request The request instance.
       
   158 	 * @return WP_REST_Response|WP_Error
       
   159 	 */
       
   160 	public function get_template_fallback( $request ) {
       
   161 		$hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] );
       
   162 
       
   163 		do {
       
   164 			$fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
       
   165 			array_shift( $hierarchy );
       
   166 		} while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) );
       
   167 
       
   168 		// To maintain original behavior, return an empty object rather than a 404 error when no template is found.
       
   169 		$response = $fallback_template ? $this->prepare_item_for_response( $fallback_template, $request ) : new stdClass();
       
   170 
       
   171 		return rest_ensure_response( $response );
       
   172 	}
       
   173 
       
   174 	/**
   121 	 * Checks if the user has permissions to make the request.
   175 	 * Checks if the user has permissions to make the request.
   122 	 *
   176 	 *
   123 	 * @since 5.8.0
   177 	 * @since 5.8.0
   124 	 *
   178 	 *
   125 	 * @param WP_REST_Request $request Full details about the request.
   179 	 * @param WP_REST_Request $request Full details about the request.
   126 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
   180 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
   127 	 */
   181 	 */
   128 	protected function permissions_check( $request ) {
   182 	protected function permissions_check( $request ) {
   129 		// Verify if the current user has edit_theme_options capability.
   183 		/*
   130 		// This capability is required to edit/view/delete templates.
   184 		 * Verify if the current user has edit_theme_options capability.
       
   185 		 * This capability is required to edit/view/delete templates.
       
   186 		 */
   131 		if ( ! current_user_can( 'edit_theme_options' ) ) {
   187 		if ( ! current_user_can( 'edit_theme_options' ) ) {
   132 			return new WP_Error(
   188 			return new WP_Error(
   133 				'rest_cannot_manage_templates',
   189 				'rest_cannot_manage_templates',
   134 				__( 'Sorry, you are not allowed to access the templates on this site.' ),
   190 				__( 'Sorry, you are not allowed to access the templates on this site.' ),
   135 				array(
   191 				array(
   178 
   234 
   179 	/**
   235 	/**
   180 	 * Checks if a given request has access to read templates.
   236 	 * Checks if a given request has access to read templates.
   181 	 *
   237 	 *
   182 	 * @since 5.8.0
   238 	 * @since 5.8.0
       
   239 	 * @since 6.6.0 Allow users with edit_posts capability to read templates.
   183 	 *
   240 	 *
   184 	 * @param WP_REST_Request $request Full details about the request.
   241 	 * @param WP_REST_Request $request Full details about the request.
   185 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
   242 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
   186 	 */
   243 	 */
   187 	public function get_items_permissions_check( $request ) {
   244 	public function get_items_permissions_check( $request ) {
   188 		return $this->permissions_check( $request );
   245 		if ( current_user_can( 'edit_posts' ) ) {
       
   246 			return true;
       
   247 		}
       
   248 		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
       
   249 			if ( current_user_can( $post_type->cap->edit_posts ) ) {
       
   250 				return true;
       
   251 			}
       
   252 		}
       
   253 
       
   254 		return new WP_Error(
       
   255 			'rest_cannot_manage_templates',
       
   256 			__( 'Sorry, you are not allowed to access the templates on this site.' ),
       
   257 			array(
       
   258 				'status' => rest_authorization_required_code(),
       
   259 			)
       
   260 		);
   189 	}
   261 	}
   190 
   262 
   191 	/**
   263 	/**
   192 	 * Returns a list of templates.
   264 	 * Returns a list of templates.
   193 	 *
   265 	 *
   219 
   291 
   220 	/**
   292 	/**
   221 	 * Checks if a given request has access to read a single template.
   293 	 * Checks if a given request has access to read a single template.
   222 	 *
   294 	 *
   223 	 * @since 5.8.0
   295 	 * @since 5.8.0
       
   296 	 * @since 6.6.0 Allow users with edit_posts capability to read individual templates.
   224 	 *
   297 	 *
   225 	 * @param WP_REST_Request $request Full details about the request.
   298 	 * @param WP_REST_Request $request Full details about the request.
   226 	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
   299 	 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
   227 	 */
   300 	 */
   228 	public function get_item_permissions_check( $request ) {
   301 	public function get_item_permissions_check( $request ) {
   229 		return $this->permissions_check( $request );
   302 		if ( current_user_can( 'edit_posts' ) ) {
       
   303 			return true;
       
   304 		}
       
   305 		foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
       
   306 			if ( current_user_can( $post_type->cap->edit_posts ) ) {
       
   307 				return true;
       
   308 			}
       
   309 		}
       
   310 
       
   311 		return new WP_Error(
       
   312 			'rest_cannot_manage_templates',
       
   313 			__( 'Sorry, you are not allowed to access the templates on this site.' ),
       
   314 			array(
       
   315 				'status' => rest_authorization_required_code(),
       
   316 			)
       
   317 		);
   230 	}
   318 	}
   231 
   319 
   232 	/**
   320 	/**
   233 	 * Returns the given template
   321 	 * Returns the given template
   234 	 *
   322 	 *
   449 					__( 'The template has already been deleted.' ),
   537 					__( 'The template has already been deleted.' ),
   450 					array( 'status' => 410 )
   538 					array( 'status' => 410 )
   451 				);
   539 				);
   452 			}
   540 			}
   453 
   541 
   454 			// (Note that internally this falls through to `wp_delete_post()`
   542 			/*
   455 			// if the Trash is disabled.)
   543 			 * (Note that internally this falls through to `wp_delete_post()`
       
   544 			 * if the Trash is disabled.)
       
   545 			 */
   456 			$result           = wp_trash_post( $id );
   546 			$result           = wp_trash_post( $id );
   457 			$template->status = 'trash';
   547 			$template->status = 'trash';
   458 			$response         = $this->prepare_item_for_response( $template, $request );
   548 			$response         = $this->prepare_item_for_response( $template, $request );
   459 		}
   549 		}
   460 
   550 
   473 	 * Prepares a single template for create or update.
   563 	 * Prepares a single template for create or update.
   474 	 *
   564 	 *
   475 	 * @since 5.8.0
   565 	 * @since 5.8.0
   476 	 *
   566 	 *
   477 	 * @param WP_REST_Request $request Request object.
   567 	 * @param WP_REST_Request $request Request object.
   478 	 * @return stdClass Changes to pass to wp_update_post.
   568 	 * @return stdClass|WP_Error Changes to pass to wp_update_post.
   479 	 */
   569 	 */
   480 	protected function prepare_item_for_database( $request ) {
   570 	protected function prepare_item_for_database( $request ) {
   481 		$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
   571 		$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
   482 		$changes  = new stdClass();
   572 		$changes  = new stdClass();
   483 		if ( null === $template ) {
   573 		if ( null === $template ) {
   484 			$changes->post_type   = $this->post_type;
   574 			$changes->post_type   = $this->post_type;
   485 			$changes->post_status = 'publish';
   575 			$changes->post_status = 'publish';
   486 			$changes->tax_input   = array(
   576 			$changes->tax_input   = array(
   487 				'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
   577 				'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(),
   488 			);
   578 			);
   489 		} elseif ( 'custom' !== $template->source ) {
   579 		} elseif ( 'custom' !== $template->source ) {
   490 			$changes->post_name   = $template->slug;
   580 			$changes->post_name   = $template->slug;
   491 			$changes->post_type   = $this->post_type;
   581 			$changes->post_type   = $this->post_type;
   492 			$changes->post_status = 'publish';
   582 			$changes->post_status = 'publish';
   523 			$changes->post_excerpt = $request['description'];
   613 			$changes->post_excerpt = $request['description'];
   524 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   614 		} elseif ( null !== $template && 'custom' !== $template->source ) {
   525 			$changes->post_excerpt = $template->description;
   615 			$changes->post_excerpt = $template->description;
   526 		}
   616 		}
   527 
   617 
       
   618 		if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) {
       
   619 			$changes->meta_input     = wp_parse_args(
       
   620 				array(
       
   621 					'is_wp_suggestion' => $request['is_wp_suggestion'],
       
   622 				),
       
   623 				$changes->meta_input = array()
       
   624 			);
       
   625 		}
       
   626 
   528 		if ( 'wp_template_part' === $this->post_type ) {
   627 		if ( 'wp_template_part' === $this->post_type ) {
   529 			if ( isset( $request['area'] ) ) {
   628 			if ( isset( $request['area'] ) ) {
   530 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
   629 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
   531 			} elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
   630 			} elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
   532 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
   631 				$changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
   533 			} elseif ( ! $template->area ) {
   632 			} elseif ( empty( $template->area ) ) {
   534 				$changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
   633 				$changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
   535 			}
   634 			}
   536 		}
   635 		}
   537 
   636 
   538 		if ( ! empty( $request['author'] ) ) {
   637 		if ( ! empty( $request['author'] ) ) {
   551 			}
   650 			}
   552 
   651 
   553 			$changes->post_author = $post_author;
   652 			$changes->post_author = $post_author;
   554 		}
   653 		}
   555 
   654 
   556 		return $changes;
   655 		/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
       
   656 		return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request );
   557 	}
   657 	}
   558 
   658 
   559 	/**
   659 	/**
   560 	 * Prepare a single template output for response
   660 	 * Prepare a single template output for response
   561 	 *
   661 	 *
   562 	 * @since 5.8.0
   662 	 * @since 5.8.0
   563 	 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
   663 	 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
       
   664 	 * @since 6.3.0 Added `modified` property to the response.
   564 	 *
   665 	 *
   565 	 * @param WP_Block_Template $item    Template instance.
   666 	 * @param WP_Block_Template $item    Template instance.
   566 	 * @param WP_REST_Request   $request Request object.
   667 	 * @param WP_REST_Request   $request Request object.
   567 	 * @return WP_REST_Response Response object.
   668 	 * @return WP_REST_Response Response object.
   568 	 */
   669 	 */
   569 	public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
   670 	public function prepare_item_for_response( $item, $request ) {
       
   671 		// Resolve pattern blocks so they don't need to be resolved client-side
       
   672 		// in the editor, improving performance.
       
   673 		$blocks        = parse_blocks( $item->content );
       
   674 		$blocks        = resolve_pattern_blocks( $blocks );
       
   675 		$item->content = serialize_blocks( $blocks );
       
   676 
   570 		// Restores the more descriptive, specific name for use within this method.
   677 		// Restores the more descriptive, specific name for use within this method.
   571 		$template = $item;
   678 		$template = $item;
   572 
   679 
   573 		$fields = $this->get_fields_for_response( $request );
   680 		$fields = $this->get_fields_for_response( $request );
   574 
   681 
   653 
   760 
   654 		if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
   761 		if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
   655 			$data['area'] = $template->area;
   762 			$data['area'] = $template->area;
   656 		}
   763 		}
   657 
   764 
       
   765 		if ( rest_is_field_included( 'modified', $fields ) ) {
       
   766 			$data['modified'] = mysql_to_rfc3339( $template->modified );
       
   767 		}
       
   768 
       
   769 		if ( rest_is_field_included( 'author_text', $fields ) ) {
       
   770 			$data['author_text'] = self::get_wp_templates_author_text_field( $template );
       
   771 		}
       
   772 
       
   773 		if ( rest_is_field_included( 'original_source', $fields ) ) {
       
   774 			$data['original_source'] = self::get_wp_templates_original_source_field( $template );
       
   775 		}
       
   776 
   658 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
   777 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
   659 		$data    = $this->add_additional_fields_to_object( $data, $request );
   778 		$data    = $this->add_additional_fields_to_object( $data, $request );
   660 		$data    = $this->filter_response_by_context( $data, $context );
   779 		$data    = $this->filter_response_by_context( $data, $context );
   661 
   780 
   662 		// Wrap the data in a response object.
   781 		// Wrap the data in a response object.
   663 		$response = rest_ensure_response( $data );
   782 		$response = rest_ensure_response( $data );
   664 
   783 
   665 		$links = $this->prepare_links( $template->id );
   784 		if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
   666 		$response->add_links( $links );
   785 			$links = $this->prepare_links( $template->id );
   667 		if ( ! empty( $links['self']['href'] ) ) {
   786 			$response->add_links( $links );
   668 			$actions = $this->get_available_actions();
   787 			if ( ! empty( $links['self']['href'] ) ) {
   669 			$self    = $links['self']['href'];
   788 				$actions = $this->get_available_actions();
   670 			foreach ( $actions as $rel ) {
   789 				$self    = $links['self']['href'];
   671 				$response->add_link( $rel, $self );
   790 				foreach ( $actions as $rel ) {
       
   791 					$response->add_link( $rel, $self );
       
   792 				}
   672 			}
   793 			}
   673 		}
   794 		}
   674 
   795 
   675 		return $response;
   796 		return $response;
       
   797 	}
       
   798 
       
   799 	/**
       
   800 	 * Returns the source from where the template originally comes from.
       
   801 	 *
       
   802 	 * @since 6.5.0
       
   803 	 *
       
   804 	 * @param WP_Block_Template $template_object Template instance.
       
   805 	 * @return string                            Original source of the template one of theme, plugin, site, or user.
       
   806 	 */
       
   807 	private static function get_wp_templates_original_source_field( $template_object ) {
       
   808 		if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) {
       
   809 			// Added by theme.
       
   810 			// Template originally provided by a theme, but customized by a user.
       
   811 			// Templates originally didn't have the 'origin' field so identify
       
   812 			// older customized templates by checking for no origin and a 'theme'
       
   813 			// or 'custom' source.
       
   814 			if ( $template_object->has_theme_file &&
       
   815 			( 'theme' === $template_object->origin || (
       
   816 				empty( $template_object->origin ) && in_array(
       
   817 					$template_object->source,
       
   818 					array(
       
   819 						'theme',
       
   820 						'custom',
       
   821 					),
       
   822 					true
       
   823 				) )
       
   824 			)
       
   825 			) {
       
   826 				return 'theme';
       
   827 			}
       
   828 
       
   829 			// Added by plugin.
       
   830 			if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) {
       
   831 				return 'plugin';
       
   832 			}
       
   833 
       
   834 			// Added by site.
       
   835 			// Template was created from scratch, but has no author. Author support
       
   836 			// was only added to templates in WordPress 5.9. Fallback to showing the
       
   837 			// site logo and title.
       
   838 			if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) {
       
   839 				return 'site';
       
   840 			}
       
   841 		}
       
   842 
       
   843 		// Added by user.
       
   844 		return 'user';
       
   845 	}
       
   846 
       
   847 	/**
       
   848 	 * Returns a human readable text for the author of the template.
       
   849 	 *
       
   850 	 * @since 6.5.0
       
   851 	 *
       
   852 	 * @param WP_Block_Template $template_object Template instance.
       
   853 	 * @return string                            Human readable text for the author.
       
   854 	 */
       
   855 	private static function get_wp_templates_author_text_field( $template_object ) {
       
   856 		$original_source = self::get_wp_templates_original_source_field( $template_object );
       
   857 		switch ( $original_source ) {
       
   858 			case 'theme':
       
   859 				$theme_name = wp_get_theme( $template_object->theme )->get( 'Name' );
       
   860 				return empty( $theme_name ) ? $template_object->theme : $theme_name;
       
   861 			case 'plugin':
       
   862 				$plugins = get_plugins();
       
   863 				$plugin  = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ];
       
   864 				return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name'];
       
   865 			case 'site':
       
   866 				return get_bloginfo( 'name' );
       
   867 			case 'user':
       
   868 				$author = get_user_by( 'id', $template_object->author );
       
   869 				if ( ! $author ) {
       
   870 					return __( 'Unknown author' );
       
   871 				}
       
   872 				return $author->get( 'display_name' );
       
   873 		}
   676 	}
   874 	}
   677 
   875 
   678 
   876 
   679 	/**
   877 	/**
   680 	 * Prepares links for the request.
   878 	 * Prepares links for the request.
   683 	 *
   881 	 *
   684 	 * @param integer $id ID.
   882 	 * @param integer $id ID.
   685 	 * @return array Links for the given post.
   883 	 * @return array Links for the given post.
   686 	 */
   884 	 */
   687 	protected function prepare_links( $id ) {
   885 	protected function prepare_links( $id ) {
   688 		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
       
   689 
       
   690 		$links = array(
   886 		$links = array(
   691 			'self'       => array(
   887 			'self'       => array(
   692 				'href' => rest_url( trailingslashit( $base ) . $id ),
   888 				'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ),
   693 			),
   889 			),
   694 			'collection' => array(
   890 			'collection' => array(
   695 				'href' => rest_url( $base ),
   891 				'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ),
   696 			),
   892 			),
   697 			'about'      => array(
   893 			'about'      => array(
   698 				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
   894 				'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
   699 			),
   895 			),
   700 		);
   896 		);
       
   897 
       
   898 		if ( post_type_supports( $this->post_type, 'revisions' ) ) {
       
   899 			$template = get_block_template( $id, $this->post_type );
       
   900 			if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) {
       
   901 				$revisions       = wp_get_latest_revision_id_and_total_count( $template->wp_id );
       
   902 				$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
       
   903 				$revisions_base  = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id );
       
   904 
       
   905 				$links['version-history'] = array(
       
   906 					'href'  => rest_url( $revisions_base ),
       
   907 					'count' => $revisions_count,
       
   908 				);
       
   909 
       
   910 				if ( $revisions_count > 0 ) {
       
   911 					$links['predecessor-version'] = array(
       
   912 						'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ),
       
   913 						'id'   => $revisions['latest_id'],
       
   914 					);
       
   915 				}
       
   916 			}
       
   917 		}
   701 
   918 
   702 		return $links;
   919 		return $links;
   703 	}
   920 	}
   704 
   921 
   705 	/**
   922 	/**
   767 		$schema = array(
   984 		$schema = array(
   768 			'$schema'    => 'http://json-schema.org/draft-04/schema#',
   985 			'$schema'    => 'http://json-schema.org/draft-04/schema#',
   769 			'title'      => $this->post_type,
   986 			'title'      => $this->post_type,
   770 			'type'       => 'object',
   987 			'type'       => 'object',
   771 			'properties' => array(
   988 			'properties' => array(
   772 				'id'             => array(
   989 				'id'              => array(
   773 					'description' => __( 'ID of template.' ),
   990 					'description' => __( 'ID of template.' ),
   774 					'type'        => 'string',
   991 					'type'        => 'string',
   775 					'context'     => array( 'embed', 'view', 'edit' ),
   992 					'context'     => array( 'embed', 'view', 'edit' ),
   776 					'readonly'    => true,
   993 					'readonly'    => true,
   777 				),
   994 				),
   778 				'slug'           => array(
   995 				'slug'            => array(
   779 					'description' => __( 'Unique slug identifying the template.' ),
   996 					'description' => __( 'Unique slug identifying the template.' ),
   780 					'type'        => 'string',
   997 					'type'        => 'string',
   781 					'context'     => array( 'embed', 'view', 'edit' ),
   998 					'context'     => array( 'embed', 'view', 'edit' ),
   782 					'required'    => true,
   999 					'required'    => true,
   783 					'minLength'   => 1,
  1000 					'minLength'   => 1,
   784 					'pattern'     => '[a-zA-Z0-9_\-]+',
  1001 					'pattern'     => '[a-zA-Z0-9_\%-]+',
   785 				),
  1002 				),
   786 				'theme'          => array(
  1003 				'theme'           => array(
   787 					'description' => __( 'Theme identifier for the template.' ),
  1004 					'description' => __( 'Theme identifier for the template.' ),
   788 					'type'        => 'string',
  1005 					'type'        => 'string',
   789 					'context'     => array( 'embed', 'view', 'edit' ),
  1006 					'context'     => array( 'embed', 'view', 'edit' ),
   790 				),
  1007 				),
   791 				'type'           => array(
  1008 				'type'            => array(
   792 					'description' => __( 'Type of template.' ),
  1009 					'description' => __( 'Type of template.' ),
   793 					'type'        => 'string',
  1010 					'type'        => 'string',
   794 					'context'     => array( 'embed', 'view', 'edit' ),
  1011 					'context'     => array( 'embed', 'view', 'edit' ),
   795 				),
  1012 				),
   796 				'source'         => array(
  1013 				'source'          => array(
   797 					'description' => __( 'Source of template' ),
  1014 					'description' => __( 'Source of template' ),
   798 					'type'        => 'string',
  1015 					'type'        => 'string',
   799 					'context'     => array( 'embed', 'view', 'edit' ),
  1016 					'context'     => array( 'embed', 'view', 'edit' ),
   800 					'readonly'    => true,
  1017 					'readonly'    => true,
   801 				),
  1018 				),
   802 				'origin'         => array(
  1019 				'origin'          => array(
   803 					'description' => __( 'Source of a customized template' ),
  1020 					'description' => __( 'Source of a customized template' ),
   804 					'type'        => 'string',
  1021 					'type'        => 'string',
   805 					'context'     => array( 'embed', 'view', 'edit' ),
  1022 					'context'     => array( 'embed', 'view', 'edit' ),
   806 					'readonly'    => true,
  1023 					'readonly'    => true,
   807 				),
  1024 				),
   808 				'content'        => array(
  1025 				'content'         => array(
   809 					'description' => __( 'Content of template.' ),
  1026 					'description' => __( 'Content of template.' ),
   810 					'type'        => array( 'object', 'string' ),
  1027 					'type'        => array( 'object', 'string' ),
   811 					'default'     => '',
  1028 					'default'     => '',
   812 					'context'     => array( 'embed', 'view', 'edit' ),
  1029 					'context'     => array( 'embed', 'view', 'edit' ),
   813 					'properties'  => array(
  1030 					'properties'  => array(
   822 							'context'     => array( 'edit' ),
  1039 							'context'     => array( 'edit' ),
   823 							'readonly'    => true,
  1040 							'readonly'    => true,
   824 						),
  1041 						),
   825 					),
  1042 					),
   826 				),
  1043 				),
   827 				'title'          => array(
  1044 				'title'           => array(
   828 					'description' => __( 'Title of template.' ),
  1045 					'description' => __( 'Title of template.' ),
   829 					'type'        => array( 'object', 'string' ),
  1046 					'type'        => array( 'object', 'string' ),
   830 					'default'     => '',
  1047 					'default'     => '',
   831 					'context'     => array( 'embed', 'view', 'edit' ),
  1048 					'context'     => array( 'embed', 'view', 'edit' ),
   832 					'properties'  => array(
  1049 					'properties'  => array(
   841 							'context'     => array( 'view', 'edit', 'embed' ),
  1058 							'context'     => array( 'view', 'edit', 'embed' ),
   842 							'readonly'    => true,
  1059 							'readonly'    => true,
   843 						),
  1060 						),
   844 					),
  1061 					),
   845 				),
  1062 				),
   846 				'description'    => array(
  1063 				'description'     => array(
   847 					'description' => __( 'Description of template.' ),
  1064 					'description' => __( 'Description of template.' ),
   848 					'type'        => 'string',
  1065 					'type'        => 'string',
   849 					'default'     => '',
  1066 					'default'     => '',
   850 					'context'     => array( 'embed', 'view', 'edit' ),
  1067 					'context'     => array( 'embed', 'view', 'edit' ),
   851 				),
  1068 				),
   852 				'status'         => array(
  1069 				'status'          => array(
   853 					'description' => __( 'Status of template.' ),
  1070 					'description' => __( 'Status of template.' ),
   854 					'type'        => 'string',
  1071 					'type'        => 'string',
   855 					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
  1072 					'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
   856 					'default'     => 'publish',
  1073 					'default'     => 'publish',
   857 					'context'     => array( 'embed', 'view', 'edit' ),
  1074 					'context'     => array( 'embed', 'view', 'edit' ),
   858 				),
  1075 				),
   859 				'wp_id'          => array(
  1076 				'wp_id'           => array(
   860 					'description' => __( 'Post ID.' ),
  1077 					'description' => __( 'Post ID.' ),
   861 					'type'        => 'integer',
  1078 					'type'        => 'integer',
   862 					'context'     => array( 'embed', 'view', 'edit' ),
  1079 					'context'     => array( 'embed', 'view', 'edit' ),
   863 					'readonly'    => true,
  1080 					'readonly'    => true,
   864 				),
  1081 				),
   865 				'has_theme_file' => array(
  1082 				'has_theme_file'  => array(
   866 					'description' => __( 'Theme file exists.' ),
  1083 					'description' => __( 'Theme file exists.' ),
   867 					'type'        => 'bool',
  1084 					'type'        => 'bool',
   868 					'context'     => array( 'embed', 'view', 'edit' ),
  1085 					'context'     => array( 'embed', 'view', 'edit' ),
   869 					'readonly'    => true,
  1086 					'readonly'    => true,
   870 				),
  1087 				),
   871 				'author'         => array(
  1088 				'author'          => array(
   872 					'description' => __( 'The ID for the author of the template.' ),
  1089 					'description' => __( 'The ID for the author of the template.' ),
   873 					'type'        => 'integer',
  1090 					'type'        => 'integer',
   874 					'context'     => array( 'view', 'edit', 'embed' ),
  1091 					'context'     => array( 'view', 'edit', 'embed' ),
       
  1092 				),
       
  1093 				'modified'        => array(
       
  1094 					'description' => __( "The date the template was last modified, in the site's timezone." ),
       
  1095 					'type'        => 'string',
       
  1096 					'format'      => 'date-time',
       
  1097 					'context'     => array( 'view', 'edit' ),
       
  1098 					'readonly'    => true,
       
  1099 				),
       
  1100 				'author_text'     => array(
       
  1101 					'type'        => 'string',
       
  1102 					'description' => __( 'Human readable text for the author.' ),
       
  1103 					'readonly'    => true,
       
  1104 					'context'     => array( 'view', 'edit', 'embed' ),
       
  1105 				),
       
  1106 				'original_source' => array(
       
  1107 					'description' => __( 'Where the template originally comes from e.g. \'theme\'' ),
       
  1108 					'type'        => 'string',
       
  1109 					'readonly'    => true,
       
  1110 					'context'     => array( 'view', 'edit', 'embed' ),
       
  1111 					'enum'        => array(
       
  1112 						'theme',
       
  1113 						'plugin',
       
  1114 						'site',
       
  1115 						'user',
       
  1116 					),
   875 				),
  1117 				),
   876 			),
  1118 			),
   877 		);
  1119 		);
   878 
  1120 
   879 		if ( 'wp_template' === $this->post_type ) {
  1121 		if ( 'wp_template' === $this->post_type ) {