Нема описа

class-wp-rest-templates-controller.php 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. <?php
  2. /**
  3. * REST API: WP_REST_Templates_Controller class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 5.8.0
  8. */
  9. /**
  10. * Base Templates REST API Controller.
  11. *
  12. * @since 5.8.0
  13. *
  14. * @see WP_REST_Controller
  15. */
  16. class WP_REST_Templates_Controller extends WP_REST_Controller {
  17. /**
  18. * Post type.
  19. *
  20. * @since 5.8.0
  21. * @var string
  22. */
  23. protected $post_type;
  24. /**
  25. * Constructor.
  26. *
  27. * @since 5.8.0
  28. *
  29. * @param string $post_type Post type.
  30. */
  31. public function __construct( $post_type ) {
  32. $this->post_type = $post_type;
  33. $obj = get_post_type_object( $post_type );
  34. $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
  35. $this->namespace = ! empty( $obj->rest_namespace ) ? $obj->rest_namespace : 'wp/v2';
  36. }
  37. /**
  38. * Registers the controllers routes.
  39. *
  40. * @since 5.8.0
  41. */
  42. public function register_routes() {
  43. // Lists all templates.
  44. register_rest_route(
  45. $this->namespace,
  46. '/' . $this->rest_base,
  47. array(
  48. array(
  49. 'methods' => WP_REST_Server::READABLE,
  50. 'callback' => array( $this, 'get_items' ),
  51. 'permission_callback' => array( $this, 'get_items_permissions_check' ),
  52. 'args' => $this->get_collection_params(),
  53. ),
  54. array(
  55. 'methods' => WP_REST_Server::CREATABLE,
  56. 'callback' => array( $this, 'create_item' ),
  57. 'permission_callback' => array( $this, 'create_item_permissions_check' ),
  58. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
  59. ),
  60. 'schema' => array( $this, 'get_public_item_schema' ),
  61. )
  62. );
  63. // Lists/updates a single template based on the given id.
  64. register_rest_route(
  65. $this->namespace,
  66. // The route.
  67. sprintf(
  68. '/%s/(?P<id>%s%s)',
  69. $this->rest_base,
  70. // Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
  71. // Excludes invalid directory name characters: `/:<>*?"|`.
  72. '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
  73. // Matches the template name.
  74. '[\/\w-]+'
  75. ),
  76. array(
  77. 'args' => array(
  78. 'id' => array(
  79. 'description' => __( 'The id of a template' ),
  80. 'type' => 'string',
  81. 'sanitize_callback' => array( $this, '_sanitize_template_id' ),
  82. ),
  83. ),
  84. array(
  85. 'methods' => WP_REST_Server::READABLE,
  86. 'callback' => array( $this, 'get_item' ),
  87. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  88. 'args' => array(
  89. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  90. ),
  91. ),
  92. array(
  93. 'methods' => WP_REST_Server::EDITABLE,
  94. 'callback' => array( $this, 'update_item' ),
  95. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  96. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  97. ),
  98. array(
  99. 'methods' => WP_REST_Server::DELETABLE,
  100. 'callback' => array( $this, 'delete_item' ),
  101. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  102. 'args' => array(
  103. 'force' => array(
  104. 'type' => 'boolean',
  105. 'default' => false,
  106. 'description' => __( 'Whether to bypass Trash and force deletion.' ),
  107. ),
  108. ),
  109. ),
  110. 'schema' => array( $this, 'get_public_item_schema' ),
  111. )
  112. );
  113. }
  114. /**
  115. * Checks if the user has permissions to make the request.
  116. *
  117. * @since 5.8.0
  118. *
  119. * @param WP_REST_Request $request Full details about the request.
  120. * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  121. */
  122. protected function permissions_check( $request ) {
  123. // Verify if the current user has edit_theme_options capability.
  124. // This capability is required to edit/view/delete templates.
  125. if ( ! current_user_can( 'edit_theme_options' ) ) {
  126. return new WP_Error(
  127. 'rest_cannot_manage_templates',
  128. __( 'Sorry, you are not allowed to access the templates on this site.' ),
  129. array(
  130. 'status' => rest_authorization_required_code(),
  131. )
  132. );
  133. }
  134. return true;
  135. }
  136. /**
  137. * Requesting this endpoint for a template like 'twentytwentytwo//home'
  138. * requires using a path like /wp/v2/templates/twentytwentytwo//home. There
  139. * are special cases when WordPress routing corrects the name to contain
  140. * only a single slash like 'twentytwentytwo/home'.
  141. *
  142. * This method doubles the last slash if it's not already doubled. It relies
  143. * on the template ID format {theme_name}//{template_slug} and the fact that
  144. * slugs cannot contain slashes.
  145. *
  146. * @since 5.9.0
  147. * @see https://core.trac.wordpress.org/ticket/54507
  148. *
  149. * @param string $id Template ID.
  150. * @return string Sanitized template ID.
  151. */
  152. public function _sanitize_template_id( $id ) {
  153. $id = urldecode( $id );
  154. $last_slash_pos = strrpos( $id, '/' );
  155. if ( false === $last_slash_pos ) {
  156. return $id;
  157. }
  158. $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/';
  159. if ( $is_double_slashed ) {
  160. return $id;
  161. }
  162. return (
  163. substr( $id, 0, $last_slash_pos )
  164. . '/'
  165. . substr( $id, $last_slash_pos )
  166. );
  167. }
  168. /**
  169. * Checks if a given request has access to read templates.
  170. *
  171. * @since 5.8.0
  172. *
  173. * @param WP_REST_Request $request Full details about the request.
  174. * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  175. */
  176. public function get_items_permissions_check( $request ) {
  177. return $this->permissions_check( $request );
  178. }
  179. /**
  180. * Returns a list of templates.
  181. *
  182. * @since 5.8.0
  183. *
  184. * @param WP_REST_Request $request The request instance.
  185. * @return WP_REST_Response
  186. */
  187. public function get_items( $request ) {
  188. $query = array();
  189. if ( isset( $request['wp_id'] ) ) {
  190. $query['wp_id'] = $request['wp_id'];
  191. }
  192. if ( isset( $request['area'] ) ) {
  193. $query['area'] = $request['area'];
  194. }
  195. if ( isset( $request['post_type'] ) ) {
  196. $query['post_type'] = $request['post_type'];
  197. }
  198. $templates = array();
  199. foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
  200. $data = $this->prepare_item_for_response( $template, $request );
  201. $templates[] = $this->prepare_response_for_collection( $data );
  202. }
  203. return rest_ensure_response( $templates );
  204. }
  205. /**
  206. * Checks if a given request has access to read a single template.
  207. *
  208. * @since 5.8.0
  209. *
  210. * @param WP_REST_Request $request Full details about the request.
  211. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
  212. */
  213. public function get_item_permissions_check( $request ) {
  214. return $this->permissions_check( $request );
  215. }
  216. /**
  217. * Returns the given template
  218. *
  219. * @since 5.8.0
  220. *
  221. * @param WP_REST_Request $request The request instance.
  222. * @return WP_REST_Response|WP_Error
  223. */
  224. public function get_item( $request ) {
  225. if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
  226. $template = get_block_file_template( $request['id'], $this->post_type );
  227. } else {
  228. $template = get_block_template( $request['id'], $this->post_type );
  229. }
  230. if ( ! $template ) {
  231. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  232. }
  233. return $this->prepare_item_for_response( $template, $request );
  234. }
  235. /**
  236. * Checks if a given request has access to write a single template.
  237. *
  238. * @since 5.8.0
  239. *
  240. * @param WP_REST_Request $request Full details about the request.
  241. * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
  242. */
  243. public function update_item_permissions_check( $request ) {
  244. return $this->permissions_check( $request );
  245. }
  246. /**
  247. * Updates a single template.
  248. *
  249. * @since 5.8.0
  250. *
  251. * @param WP_REST_Request $request Full details about the request.
  252. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  253. */
  254. public function update_item( $request ) {
  255. $template = get_block_template( $request['id'], $this->post_type );
  256. if ( ! $template ) {
  257. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  258. }
  259. $post_before = get_post( $template->wp_id );
  260. if ( isset( $request['source'] ) && 'theme' === $request['source'] ) {
  261. wp_delete_post( $template->wp_id, true );
  262. $request->set_param( 'context', 'edit' );
  263. $template = get_block_template( $request['id'], $this->post_type );
  264. $response = $this->prepare_item_for_response( $template, $request );
  265. return rest_ensure_response( $response );
  266. }
  267. $changes = $this->prepare_item_for_database( $request );
  268. if ( is_wp_error( $changes ) ) {
  269. return $changes;
  270. }
  271. if ( 'custom' === $template->source ) {
  272. $update = true;
  273. $result = wp_update_post( wp_slash( (array) $changes ), false );
  274. } else {
  275. $update = false;
  276. $post_before = null;
  277. $result = wp_insert_post( wp_slash( (array) $changes ), false );
  278. }
  279. if ( is_wp_error( $result ) ) {
  280. if ( 'db_update_error' === $result->get_error_code() ) {
  281. $result->add_data( array( 'status' => 500 ) );
  282. } else {
  283. $result->add_data( array( 'status' => 400 ) );
  284. }
  285. return $result;
  286. }
  287. $template = get_block_template( $request['id'], $this->post_type );
  288. $fields_update = $this->update_additional_fields_for_object( $template, $request );
  289. if ( is_wp_error( $fields_update ) ) {
  290. return $fields_update;
  291. }
  292. $request->set_param( 'context', 'edit' );
  293. $post = get_post( $template->wp_id );
  294. /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
  295. do_action( "rest_after_insert_{$this->post_type}", $post, $request, false );
  296. wp_after_insert_post( $post, $update, $post_before );
  297. $response = $this->prepare_item_for_response( $template, $request );
  298. return rest_ensure_response( $response );
  299. }
  300. /**
  301. * Checks if a given request has access to create a template.
  302. *
  303. * @since 5.8.0
  304. *
  305. * @param WP_REST_Request $request Full details about the request.
  306. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
  307. */
  308. public function create_item_permissions_check( $request ) {
  309. return $this->permissions_check( $request );
  310. }
  311. /**
  312. * Creates a single template.
  313. *
  314. * @since 5.8.0
  315. *
  316. * @param WP_REST_Request $request Full details about the request.
  317. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  318. */
  319. public function create_item( $request ) {
  320. $prepared_post = $this->prepare_item_for_database( $request );
  321. if ( is_wp_error( $prepared_post ) ) {
  322. return $prepared_post;
  323. }
  324. $prepared_post->post_name = $request['slug'];
  325. $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true );
  326. if ( is_wp_error( $post_id ) ) {
  327. if ( 'db_insert_error' === $post_id->get_error_code() ) {
  328. $post_id->add_data( array( 'status' => 500 ) );
  329. } else {
  330. $post_id->add_data( array( 'status' => 400 ) );
  331. }
  332. return $post_id;
  333. }
  334. $posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type );
  335. if ( ! count( $posts ) ) {
  336. return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) );
  337. }
  338. $id = $posts[0]->id;
  339. $post = get_post( $post_id );
  340. $template = get_block_template( $id, $this->post_type );
  341. $fields_update = $this->update_additional_fields_for_object( $template, $request );
  342. if ( is_wp_error( $fields_update ) ) {
  343. return $fields_update;
  344. }
  345. /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
  346. do_action( "rest_after_insert_{$this->post_type}", $post, $request, true );
  347. wp_after_insert_post( $post, false, null );
  348. $response = $this->prepare_item_for_response( $template, $request );
  349. $response = rest_ensure_response( $response );
  350. $response->set_status( 201 );
  351. $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) );
  352. return $response;
  353. }
  354. /**
  355. * Checks if a given request has access to delete a single template.
  356. *
  357. * @since 5.8.0
  358. *
  359. * @param WP_REST_Request $request Full details about the request.
  360. * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
  361. */
  362. public function delete_item_permissions_check( $request ) {
  363. return $this->permissions_check( $request );
  364. }
  365. /**
  366. * Deletes a single template.
  367. *
  368. * @since 5.8.0
  369. *
  370. * @param WP_REST_Request $request Full details about the request.
  371. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  372. */
  373. public function delete_item( $request ) {
  374. $template = get_block_template( $request['id'], $this->post_type );
  375. if ( ! $template ) {
  376. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  377. }
  378. if ( 'custom' !== $template->source ) {
  379. return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
  380. }
  381. $id = $template->wp_id;
  382. $force = (bool) $request['force'];
  383. $request->set_param( 'context', 'edit' );
  384. // If we're forcing, then delete permanently.
  385. if ( $force ) {
  386. $previous = $this->prepare_item_for_response( $template, $request );
  387. $result = wp_delete_post( $id, true );
  388. $response = new WP_REST_Response();
  389. $response->set_data(
  390. array(
  391. 'deleted' => true,
  392. 'previous' => $previous->get_data(),
  393. )
  394. );
  395. } else {
  396. // Otherwise, only trash if we haven't already.
  397. if ( 'trash' === $template->status ) {
  398. return new WP_Error(
  399. 'rest_template_already_trashed',
  400. __( 'The template has already been deleted.' ),
  401. array( 'status' => 410 )
  402. );
  403. }
  404. // (Note that internally this falls through to `wp_delete_post()`
  405. // if the Trash is disabled.)
  406. $result = wp_trash_post( $id );
  407. $template->status = 'trash';
  408. $response = $this->prepare_item_for_response( $template, $request );
  409. }
  410. if ( ! $result ) {
  411. return new WP_Error(
  412. 'rest_cannot_delete',
  413. __( 'The template cannot be deleted.' ),
  414. array( 'status' => 500 )
  415. );
  416. }
  417. return $response;
  418. }
  419. /**
  420. * Prepares a single template for create or update.
  421. *
  422. * @since 5.8.0
  423. *
  424. * @param WP_REST_Request $request Request object.
  425. * @return stdClass Changes to pass to wp_update_post.
  426. */
  427. protected function prepare_item_for_database( $request ) {
  428. $template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
  429. $changes = new stdClass();
  430. if ( null === $template ) {
  431. $changes->post_type = $this->post_type;
  432. $changes->post_status = 'publish';
  433. $changes->tax_input = array(
  434. 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
  435. );
  436. } elseif ( 'custom' !== $template->source ) {
  437. $changes->post_name = $template->slug;
  438. $changes->post_type = $this->post_type;
  439. $changes->post_status = 'publish';
  440. $changes->tax_input = array(
  441. 'wp_theme' => $template->theme,
  442. );
  443. $changes->meta_input = array(
  444. 'origin' => $template->source,
  445. );
  446. } else {
  447. $changes->post_name = $template->slug;
  448. $changes->ID = $template->wp_id;
  449. $changes->post_status = 'publish';
  450. }
  451. if ( isset( $request['content'] ) ) {
  452. if ( is_string( $request['content'] ) ) {
  453. $changes->post_content = $request['content'];
  454. } elseif ( isset( $request['content']['raw'] ) ) {
  455. $changes->post_content = $request['content']['raw'];
  456. }
  457. } elseif ( null !== $template && 'custom' !== $template->source ) {
  458. $changes->post_content = $template->content;
  459. }
  460. if ( isset( $request['title'] ) ) {
  461. if ( is_string( $request['title'] ) ) {
  462. $changes->post_title = $request['title'];
  463. } elseif ( ! empty( $request['title']['raw'] ) ) {
  464. $changes->post_title = $request['title']['raw'];
  465. }
  466. } elseif ( null !== $template && 'custom' !== $template->source ) {
  467. $changes->post_title = $template->title;
  468. }
  469. if ( isset( $request['description'] ) ) {
  470. $changes->post_excerpt = $request['description'];
  471. } elseif ( null !== $template && 'custom' !== $template->source ) {
  472. $changes->post_excerpt = $template->description;
  473. }
  474. if ( 'wp_template_part' === $this->post_type ) {
  475. if ( isset( $request['area'] ) ) {
  476. $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] );
  477. } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) {
  478. $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area );
  479. } elseif ( ! $template->area ) {
  480. $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
  481. }
  482. }
  483. if ( ! empty( $request['author'] ) ) {
  484. $post_author = (int) $request['author'];
  485. if ( get_current_user_id() !== $post_author ) {
  486. $user_obj = get_userdata( $post_author );
  487. if ( ! $user_obj ) {
  488. return new WP_Error(
  489. 'rest_invalid_author',
  490. __( 'Invalid author ID.' ),
  491. array( 'status' => 400 )
  492. );
  493. }
  494. }
  495. $changes->post_author = $post_author;
  496. }
  497. return $changes;
  498. }
  499. /**
  500. * Prepare a single template output for response
  501. *
  502. * @since 5.8.0
  503. * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support.
  504. *
  505. * @param WP_Block_Template $item Template instance.
  506. * @param WP_REST_Request $request Request object.
  507. * @return WP_REST_Response Response object.
  508. */
  509. public function prepare_item_for_response( $item, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
  510. // Restores the more descriptive, specific name for use within this method.
  511. $template = $item;
  512. $fields = $this->get_fields_for_response( $request );
  513. // Base fields for every template.
  514. $data = array();
  515. if ( rest_is_field_included( 'id', $fields ) ) {
  516. $data['id'] = $template->id;
  517. }
  518. if ( rest_is_field_included( 'theme', $fields ) ) {
  519. $data['theme'] = $template->theme;
  520. }
  521. if ( rest_is_field_included( 'content', $fields ) ) {
  522. $data['content'] = array();
  523. }
  524. if ( rest_is_field_included( 'content.raw', $fields ) ) {
  525. $data['content']['raw'] = $template->content;
  526. }
  527. if ( rest_is_field_included( 'content.block_version', $fields ) ) {
  528. $data['content']['block_version'] = block_version( $template->content );
  529. }
  530. if ( rest_is_field_included( 'slug', $fields ) ) {
  531. $data['slug'] = $template->slug;
  532. }
  533. if ( rest_is_field_included( 'source', $fields ) ) {
  534. $data['source'] = $template->source;
  535. }
  536. if ( rest_is_field_included( 'origin', $fields ) ) {
  537. $data['origin'] = $template->origin;
  538. }
  539. if ( rest_is_field_included( 'type', $fields ) ) {
  540. $data['type'] = $template->type;
  541. }
  542. if ( rest_is_field_included( 'description', $fields ) ) {
  543. $data['description'] = $template->description;
  544. }
  545. if ( rest_is_field_included( 'title', $fields ) ) {
  546. $data['title'] = array();
  547. }
  548. if ( rest_is_field_included( 'title.raw', $fields ) ) {
  549. $data['title']['raw'] = $template->title;
  550. }
  551. if ( rest_is_field_included( 'title.rendered', $fields ) ) {
  552. if ( $template->wp_id ) {
  553. /** This filter is documented in wp-includes/post-template.php */
  554. $data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id );
  555. } else {
  556. $data['title']['rendered'] = $template->title;
  557. }
  558. }
  559. if ( rest_is_field_included( 'status', $fields ) ) {
  560. $data['status'] = $template->status;
  561. }
  562. if ( rest_is_field_included( 'wp_id', $fields ) ) {
  563. $data['wp_id'] = (int) $template->wp_id;
  564. }
  565. if ( rest_is_field_included( 'has_theme_file', $fields ) ) {
  566. $data['has_theme_file'] = (bool) $template->has_theme_file;
  567. }
  568. if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) {
  569. $data['is_custom'] = $template->is_custom;
  570. }
  571. if ( rest_is_field_included( 'author', $fields ) ) {
  572. $data['author'] = (int) $template->author;
  573. }
  574. if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) {
  575. $data['area'] = $template->area;
  576. }
  577. $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
  578. $data = $this->add_additional_fields_to_object( $data, $request );
  579. $data = $this->filter_response_by_context( $data, $context );
  580. // Wrap the data in a response object.
  581. $response = rest_ensure_response( $data );
  582. $links = $this->prepare_links( $template->id );
  583. $response->add_links( $links );
  584. if ( ! empty( $links['self']['href'] ) ) {
  585. $actions = $this->get_available_actions();
  586. $self = $links['self']['href'];
  587. foreach ( $actions as $rel ) {
  588. $response->add_link( $rel, $self );
  589. }
  590. }
  591. return $response;
  592. }
  593. /**
  594. * Prepares links for the request.
  595. *
  596. * @since 5.8.0
  597. *
  598. * @param integer $id ID.
  599. * @return array Links for the given post.
  600. */
  601. protected function prepare_links( $id ) {
  602. $base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
  603. $links = array(
  604. 'self' => array(
  605. 'href' => rest_url( trailingslashit( $base ) . $id ),
  606. ),
  607. 'collection' => array(
  608. 'href' => rest_url( $base ),
  609. ),
  610. 'about' => array(
  611. 'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
  612. ),
  613. );
  614. return $links;
  615. }
  616. /**
  617. * Get the link relations available for the post and current user.
  618. *
  619. * @since 5.8.0
  620. *
  621. * @return string[] List of link relations.
  622. */
  623. protected function get_available_actions() {
  624. $rels = array();
  625. $post_type = get_post_type_object( $this->post_type );
  626. if ( current_user_can( $post_type->cap->publish_posts ) ) {
  627. $rels[] = 'https://api.w.org/action-publish';
  628. }
  629. if ( current_user_can( 'unfiltered_html' ) ) {
  630. $rels[] = 'https://api.w.org/action-unfiltered-html';
  631. }
  632. return $rels;
  633. }
  634. /**
  635. * Retrieves the query params for the posts collection.
  636. *
  637. * @since 5.8.0
  638. * @since 5.9.0 Added `'area'` and `'post_type'`.
  639. *
  640. * @return array Collection parameters.
  641. */
  642. public function get_collection_params() {
  643. return array(
  644. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),
  645. 'wp_id' => array(
  646. 'description' => __( 'Limit to the specified post id.' ),
  647. 'type' => 'integer',
  648. ),
  649. 'area' => array(
  650. 'description' => __( 'Limit to the specified template part area.' ),
  651. 'type' => 'string',
  652. ),
  653. 'post_type' => array(
  654. 'description' => __( 'Post type to get the templates for.' ),
  655. 'type' => 'string',
  656. ),
  657. );
  658. }
  659. /**
  660. * Retrieves the block type' schema, conforming to JSON Schema.
  661. *
  662. * @since 5.8.0
  663. * @since 5.9.0 Added `'area'`.
  664. *
  665. * @return array Item schema data.
  666. */
  667. public function get_item_schema() {
  668. if ( $this->schema ) {
  669. return $this->add_additional_fields_schema( $this->schema );
  670. }
  671. $schema = array(
  672. '$schema' => 'http://json-schema.org/draft-04/schema#',
  673. 'title' => $this->post_type,
  674. 'type' => 'object',
  675. 'properties' => array(
  676. 'id' => array(
  677. 'description' => __( 'ID of template.' ),
  678. 'type' => 'string',
  679. 'context' => array( 'embed', 'view', 'edit' ),
  680. 'readonly' => true,
  681. ),
  682. 'slug' => array(
  683. 'description' => __( 'Unique slug identifying the template.' ),
  684. 'type' => 'string',
  685. 'context' => array( 'embed', 'view', 'edit' ),
  686. 'required' => true,
  687. 'minLength' => 1,
  688. 'pattern' => '[a-zA-Z0-9_\-]+',
  689. ),
  690. 'theme' => array(
  691. 'description' => __( 'Theme identifier for the template.' ),
  692. 'type' => 'string',
  693. 'context' => array( 'embed', 'view', 'edit' ),
  694. ),
  695. 'type' => array(
  696. 'description' => __( 'Type of template.' ),
  697. 'type' => 'string',
  698. 'context' => array( 'embed', 'view', 'edit' ),
  699. ),
  700. 'source' => array(
  701. 'description' => __( 'Source of template' ),
  702. 'type' => 'string',
  703. 'context' => array( 'embed', 'view', 'edit' ),
  704. 'readonly' => true,
  705. ),
  706. 'origin' => array(
  707. 'description' => __( 'Source of a customized template' ),
  708. 'type' => 'string',
  709. 'context' => array( 'embed', 'view', 'edit' ),
  710. 'readonly' => true,
  711. ),
  712. 'content' => array(
  713. 'description' => __( 'Content of template.' ),
  714. 'type' => array( 'object', 'string' ),
  715. 'default' => '',
  716. 'context' => array( 'embed', 'view', 'edit' ),
  717. 'properties' => array(
  718. 'raw' => array(
  719. 'description' => __( 'Content for the template, as it exists in the database.' ),
  720. 'type' => 'string',
  721. 'context' => array( 'view', 'edit' ),
  722. ),
  723. 'block_version' => array(
  724. 'description' => __( 'Version of the content block format used by the template.' ),
  725. 'type' => 'integer',
  726. 'context' => array( 'edit' ),
  727. 'readonly' => true,
  728. ),
  729. ),
  730. ),
  731. 'title' => array(
  732. 'description' => __( 'Title of template.' ),
  733. 'type' => array( 'object', 'string' ),
  734. 'default' => '',
  735. 'context' => array( 'embed', 'view', 'edit' ),
  736. 'properties' => array(
  737. 'raw' => array(
  738. 'description' => __( 'Title for the template, as it exists in the database.' ),
  739. 'type' => 'string',
  740. 'context' => array( 'view', 'edit', 'embed' ),
  741. ),
  742. 'rendered' => array(
  743. 'description' => __( 'HTML title for the template, transformed for display.' ),
  744. 'type' => 'string',
  745. 'context' => array( 'view', 'edit', 'embed' ),
  746. 'readonly' => true,
  747. ),
  748. ),
  749. ),
  750. 'description' => array(
  751. 'description' => __( 'Description of template.' ),
  752. 'type' => 'string',
  753. 'default' => '',
  754. 'context' => array( 'embed', 'view', 'edit' ),
  755. ),
  756. 'status' => array(
  757. 'description' => __( 'Status of template.' ),
  758. 'type' => 'string',
  759. 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),
  760. 'default' => 'publish',
  761. 'context' => array( 'embed', 'view', 'edit' ),
  762. ),
  763. 'wp_id' => array(
  764. 'description' => __( 'Post ID.' ),
  765. 'type' => 'integer',
  766. 'context' => array( 'embed', 'view', 'edit' ),
  767. 'readonly' => true,
  768. ),
  769. 'has_theme_file' => array(
  770. 'description' => __( 'Theme file exists.' ),
  771. 'type' => 'bool',
  772. 'context' => array( 'embed', 'view', 'edit' ),
  773. 'readonly' => true,
  774. ),
  775. 'author' => array(
  776. 'description' => __( 'The ID for the author of the template.' ),
  777. 'type' => 'integer',
  778. 'context' => array( 'view', 'edit', 'embed' ),
  779. ),
  780. ),
  781. );
  782. if ( 'wp_template' === $this->post_type ) {
  783. $schema['properties']['is_custom'] = array(
  784. 'description' => __( 'Whether a template is a custom template.' ),
  785. 'type' => 'bool',
  786. 'context' => array( 'embed', 'view', 'edit' ),
  787. 'readonly' => true,
  788. );
  789. }
  790. if ( 'wp_template_part' === $this->post_type ) {
  791. $schema['properties']['area'] = array(
  792. 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ),
  793. 'type' => 'string',
  794. 'context' => array( 'embed', 'view', 'edit' ),
  795. );
  796. }
  797. $this->schema = $schema;
  798. return $this->add_additional_fields_schema( $this->schema );
  799. }
  800. }