Nessuna descrizione

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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  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. $this->namespace = 'wp/v2';
  34. $obj = get_post_type_object( $post_type );
  35. $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
  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. '/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
  67. array(
  68. array(
  69. 'methods' => WP_REST_Server::READABLE,
  70. 'callback' => array( $this, 'get_item' ),
  71. 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  72. 'args' => array(
  73. 'id' => array(
  74. 'description' => __( 'The id of a template' ),
  75. 'type' => 'string',
  76. ),
  77. ),
  78. ),
  79. array(
  80. 'methods' => WP_REST_Server::EDITABLE,
  81. 'callback' => array( $this, 'update_item' ),
  82. 'permission_callback' => array( $this, 'update_item_permissions_check' ),
  83. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  84. ),
  85. array(
  86. 'methods' => WP_REST_Server::DELETABLE,
  87. 'callback' => array( $this, 'delete_item' ),
  88. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),
  89. 'args' => array(
  90. 'force' => array(
  91. 'type' => 'boolean',
  92. 'default' => false,
  93. 'description' => __( 'Whether to bypass Trash and force deletion.' ),
  94. ),
  95. ),
  96. ),
  97. 'schema' => array( $this, 'get_public_item_schema' ),
  98. )
  99. );
  100. }
  101. /**
  102. * Checks if the user has permissions to make the request.
  103. *
  104. * @since 5.8.0
  105. *
  106. * @param WP_REST_Request $request Full details about the request.
  107. * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  108. */
  109. protected function permissions_check( $request ) {
  110. // Verify if the current user has edit_theme_options capability.
  111. // This capability is required to edit/view/delete templates.
  112. if ( ! current_user_can( 'edit_theme_options' ) ) {
  113. return new WP_Error(
  114. 'rest_cannot_manage_templates',
  115. __( 'Sorry, you are not allowed to access the templates on this site.' ),
  116. array(
  117. 'status' => rest_authorization_required_code(),
  118. )
  119. );
  120. }
  121. return true;
  122. }
  123. /**
  124. * Checks if a given request has access to read templates.
  125. *
  126. * @since 5.8.0
  127. *
  128. * @param WP_REST_Request $request Full details about the request.
  129. * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  130. */
  131. public function get_items_permissions_check( $request ) {
  132. return $this->permissions_check( $request );
  133. }
  134. /**
  135. * Returns a list of templates.
  136. *
  137. * @since 5.8.0
  138. *
  139. * @param WP_REST_Request $request The request instance.
  140. * @return WP_REST_Response
  141. */
  142. public function get_items( $request ) {
  143. $query = array();
  144. if ( isset( $request['wp_id'] ) ) {
  145. $query['wp_id'] = $request['wp_id'];
  146. }
  147. if ( isset( $request['area'] ) ) {
  148. $query['area'] = $request['area'];
  149. }
  150. $templates = array();
  151. foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
  152. $data = $this->prepare_item_for_response( $template, $request );
  153. $templates[] = $this->prepare_response_for_collection( $data );
  154. }
  155. return rest_ensure_response( $templates );
  156. }
  157. /**
  158. * Checks if a given request has access to read a single template.
  159. *
  160. * @since 5.8.0
  161. *
  162. * @param WP_REST_Request $request Full details about the request.
  163. * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
  164. */
  165. public function get_item_permissions_check( $request ) {
  166. return $this->permissions_check( $request );
  167. }
  168. /**
  169. * Returns the given template
  170. *
  171. * @since 5.8.0
  172. *
  173. * @param WP_REST_Request $request The request instance.
  174. * @return WP_REST_Response|WP_Error
  175. */
  176. public function get_item( $request ) {
  177. $template = get_block_template( $request['id'], $this->post_type );
  178. if ( ! $template ) {
  179. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  180. }
  181. return $this->prepare_item_for_response( $template, $request );
  182. }
  183. /**
  184. * Checks if a given request has access to write a single template.
  185. *
  186. * @since 5.8.0
  187. *
  188. * @param WP_REST_Request $request Full details about the request.
  189. * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
  190. */
  191. public function update_item_permissions_check( $request ) {
  192. return $this->permissions_check( $request );
  193. }
  194. /**
  195. * Updates a single template.
  196. *
  197. * @since 5.8.0
  198. *
  199. * @param WP_REST_Request $request Full details about the request.
  200. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  201. */
  202. public function update_item( $request ) {
  203. $template = get_block_template( $request['id'], $this->post_type );
  204. if ( ! $template ) {
  205. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  206. }
  207. $changes = $this->prepare_item_for_database( $request );
  208. if ( 'custom' === $template->source ) {
  209. $result = wp_update_post( wp_slash( (array) $changes ), true );
  210. } else {
  211. $result = wp_insert_post( wp_slash( (array) $changes ), true );
  212. }
  213. if ( is_wp_error( $result ) ) {
  214. return $result;
  215. }
  216. $template = get_block_template( $request['id'], $this->post_type );
  217. $fields_update = $this->update_additional_fields_for_object( $template, $request );
  218. if ( is_wp_error( $fields_update ) ) {
  219. return $fields_update;
  220. }
  221. return $this->prepare_item_for_response(
  222. get_block_template( $request['id'], $this->post_type ),
  223. $request
  224. );
  225. }
  226. /**
  227. * Checks if a given request has access to create a template.
  228. *
  229. * @since 5.8.0
  230. *
  231. * @param WP_REST_Request $request Full details about the request.
  232. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
  233. */
  234. public function create_item_permissions_check( $request ) {
  235. return $this->permissions_check( $request );
  236. }
  237. /**
  238. * Creates a single template.
  239. *
  240. * @since 5.8.0
  241. *
  242. * @param WP_REST_Request $request Full details about the request.
  243. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  244. */
  245. public function create_item( $request ) {
  246. $changes = $this->prepare_item_for_database( $request );
  247. $changes->post_name = $request['slug'];
  248. $result = wp_insert_post( wp_slash( (array) $changes ), true );
  249. if ( is_wp_error( $result ) ) {
  250. return $result;
  251. }
  252. $posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
  253. if ( ! count( $posts ) ) {
  254. return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
  255. }
  256. $id = $posts[0]->id;
  257. $template = get_block_template( $id, $this->post_type );
  258. $fields_update = $this->update_additional_fields_for_object( $template, $request );
  259. if ( is_wp_error( $fields_update ) ) {
  260. return $fields_update;
  261. }
  262. return $this->prepare_item_for_response(
  263. get_block_template( $id, $this->post_type ),
  264. $request
  265. );
  266. }
  267. /**
  268. * Checks if a given request has access to delete a single template.
  269. *
  270. * @since 5.8.0
  271. *
  272. * @param WP_REST_Request $request Full details about the request.
  273. * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
  274. */
  275. public function delete_item_permissions_check( $request ) {
  276. return $this->permissions_check( $request );
  277. }
  278. /**
  279. * Deletes a single template.
  280. *
  281. * @since 5.8.0
  282. *
  283. * @param WP_REST_Request $request Full details about the request.
  284. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
  285. */
  286. public function delete_item( $request ) {
  287. $template = get_block_template( $request['id'], $this->post_type );
  288. if ( ! $template ) {
  289. return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
  290. }
  291. if ( 'custom' !== $template->source ) {
  292. return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
  293. }
  294. $id = $template->wp_id;
  295. $force = (bool) $request['force'];
  296. // If we're forcing, then delete permanently.
  297. if ( $force ) {
  298. $previous = $this->prepare_item_for_response( $template, $request );
  299. wp_delete_post( $id, true );
  300. $response = new WP_REST_Response();
  301. $response->set_data(
  302. array(
  303. 'deleted' => true,
  304. 'previous' => $previous->get_data(),
  305. )
  306. );
  307. return $response;
  308. }
  309. // Otherwise, only trash if we haven't already.
  310. if ( 'trash' === $template->status ) {
  311. return new WP_Error(
  312. 'rest_template_already_trashed',
  313. __( 'The template has already been deleted.' ),
  314. array( 'status' => 410 )
  315. );
  316. }
  317. wp_trash_post( $id );
  318. $template->status = 'trash';
  319. return $this->prepare_item_for_response( $template, $request );
  320. }
  321. /**
  322. * Prepares a single template for create or update.
  323. *
  324. * @since 5.8.0
  325. *
  326. * @param WP_REST_Request $request Request object.
  327. * @return stdClass Changes to pass to wp_update_post.
  328. */
  329. protected function prepare_item_for_database( $request ) {
  330. $template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
  331. $changes = new stdClass();
  332. if ( null === $template ) {
  333. $changes->post_type = $this->post_type;
  334. $changes->post_status = 'publish';
  335. $changes->tax_input = array(
  336. 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
  337. );
  338. } elseif ( 'custom' !== $template->source ) {
  339. $changes->post_name = $template->slug;
  340. $changes->post_type = $this->post_type;
  341. $changes->post_status = 'publish';
  342. $changes->tax_input = array(
  343. 'wp_theme' => $template->theme,
  344. );
  345. } else {
  346. $changes->post_name = $template->slug;
  347. $changes->ID = $template->wp_id;
  348. $changes->post_status = 'publish';
  349. }
  350. if ( isset( $request['content'] ) ) {
  351. $changes->post_content = $request['content'];
  352. } elseif ( null !== $template && 'custom' !== $template->source ) {
  353. $changes->post_content = $template->content;
  354. }
  355. if ( isset( $request['title'] ) ) {
  356. $changes->post_title = $request['title'];
  357. } elseif ( null !== $template && 'custom' !== $template->source ) {
  358. $changes->post_title = $template->title;
  359. }
  360. if ( isset( $request['description'] ) ) {
  361. $changes->post_excerpt = $request['description'];
  362. } elseif ( null !== $template && 'custom' !== $template->source ) {
  363. $changes->post_excerpt = $template->description;
  364. }
  365. return $changes;
  366. }
  367. /**
  368. * Prepare a single template output for response
  369. *
  370. * @since 5.8.0
  371. *
  372. * @param WP_Block_Template $template Template instance.
  373. * @param WP_REST_Request $request Request object.
  374. * @return WP_REST_Response $data
  375. */
  376. public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
  377. $result = array(
  378. 'id' => $template->id,
  379. 'theme' => $template->theme,
  380. 'content' => array( 'raw' => $template->content ),
  381. 'slug' => $template->slug,
  382. 'source' => $template->source,
  383. 'type' => $template->type,
  384. 'description' => $template->description,
  385. 'title' => array(
  386. 'raw' => $template->title,
  387. 'rendered' => $template->title,
  388. ),
  389. 'status' => $template->status,
  390. 'wp_id' => $template->wp_id,
  391. 'has_theme_file' => $template->has_theme_file,
  392. );
  393. if ( 'wp_template_part' === $template->type ) {
  394. $result['area'] = $template->area;
  395. }
  396. $result = $this->add_additional_fields_to_object( $result, $request );
  397. $response = rest_ensure_response( $result );
  398. $links = $this->prepare_links( $template->id );
  399. $response->add_links( $links );
  400. if ( ! empty( $links['self']['href'] ) ) {
  401. $actions = $this->get_available_actions();
  402. $self = $links['self']['href'];
  403. foreach ( $actions as $rel ) {
  404. $response->add_link( $rel, $self );
  405. }
  406. }
  407. return $response;
  408. }
  409. /**
  410. * Prepares links for the request.
  411. *
  412. * @since 5.8.0
  413. *
  414. * @param integer $id ID.
  415. * @return array Links for the given post.
  416. */
  417. protected function prepare_links( $id ) {
  418. $base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
  419. $links = array(
  420. 'self' => array(
  421. 'href' => rest_url( trailingslashit( $base ) . $id ),
  422. ),
  423. 'collection' => array(
  424. 'href' => rest_url( $base ),
  425. ),
  426. 'about' => array(
  427. 'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
  428. ),
  429. );
  430. return $links;
  431. }
  432. /**
  433. * Get the link relations available for the post and current user.
  434. *
  435. * @since 5.8.0
  436. *
  437. * @return array List of link relations.
  438. */
  439. protected function get_available_actions() {
  440. $rels = array();
  441. $post_type = get_post_type_object( $this->post_type );
  442. if ( current_user_can( $post_type->cap->publish_posts ) ) {
  443. $rels[] = 'https://api.w.org/action-publish';
  444. }
  445. if ( current_user_can( 'unfiltered_html' ) ) {
  446. $rels[] = 'https://api.w.org/action-unfiltered-html';
  447. }
  448. return $rels;
  449. }
  450. /**
  451. * Retrieves the query params for the posts collection.
  452. *
  453. * @since 5.8.0
  454. *
  455. * @return array Collection parameters.
  456. */
  457. public function get_collection_params() {
  458. return array(
  459. 'context' => $this->get_context_param(),
  460. 'wp_id' => array(
  461. 'description' => __( 'Limit to the specified post id.' ),
  462. 'type' => 'integer',
  463. ),
  464. );
  465. }
  466. /**
  467. * Retrieves the block type' schema, conforming to JSON Schema.
  468. *
  469. * @since 5.8.0
  470. *
  471. * @return array Item schema data.
  472. */
  473. public function get_item_schema() {
  474. if ( $this->schema ) {
  475. return $this->add_additional_fields_schema( $this->schema );
  476. }
  477. $schema = array(
  478. '$schema' => 'http://json-schema.org/draft-04/schema#',
  479. 'title' => $this->post_type,
  480. 'type' => 'object',
  481. 'properties' => array(
  482. 'id' => array(
  483. 'description' => __( 'ID of template.' ),
  484. 'type' => 'string',
  485. 'context' => array( 'embed', 'view', 'edit' ),
  486. 'readonly' => true,
  487. ),
  488. 'slug' => array(
  489. 'description' => __( 'Unique slug identifying the template.' ),
  490. 'type' => 'string',
  491. 'context' => array( 'embed', 'view', 'edit' ),
  492. 'required' => true,
  493. 'minLength' => 1,
  494. 'pattern' => '[a-zA-Z_\-]+',
  495. ),
  496. 'theme' => array(
  497. 'description' => __( 'Theme identifier for the template.' ),
  498. 'type' => 'string',
  499. 'context' => array( 'embed', 'view', 'edit' ),
  500. ),
  501. 'source' => array(
  502. 'description' => __( 'Source of template' ),
  503. 'type' => 'string',
  504. 'context' => array( 'embed', 'view', 'edit' ),
  505. 'readonly' => true,
  506. ),
  507. 'content' => array(
  508. 'description' => __( 'Content of template.' ),
  509. 'type' => array( 'object', 'string' ),
  510. 'default' => '',
  511. 'context' => array( 'embed', 'view', 'edit' ),
  512. ),
  513. 'title' => array(
  514. 'description' => __( 'Title of template.' ),
  515. 'type' => array( 'object', 'string' ),
  516. 'default' => '',
  517. 'context' => array( 'embed', 'view', 'edit' ),
  518. ),
  519. 'description' => array(
  520. 'description' => __( 'Description of template.' ),
  521. 'type' => 'string',
  522. 'default' => '',
  523. 'context' => array( 'embed', 'view', 'edit' ),
  524. ),
  525. 'status' => array(
  526. 'description' => __( 'Status of template.' ),
  527. 'type' => 'string',
  528. 'default' => 'publish',
  529. 'context' => array( 'embed', 'view', 'edit' ),
  530. ),
  531. 'wp_id' => array(
  532. 'description' => __( 'Post ID.' ),
  533. 'type' => 'integer',
  534. 'context' => array( 'embed', 'view', 'edit' ),
  535. 'readonly' => true,
  536. ),
  537. 'has_theme_file' => array(
  538. 'description' => __( 'Theme file exists.' ),
  539. 'type' => 'bool',
  540. 'context' => array( 'embed', 'view', 'edit' ),
  541. 'readonly' => true,
  542. ),
  543. ),
  544. );
  545. $this->schema = $schema;
  546. return $this->add_additional_fields_schema( $this->schema );
  547. }
  548. }