[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 /** 11 * Base Templates REST API Controller. 12 * 13 * @since 5.8.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Templates_Controller extends WP_REST_Controller { 18 19 /** 20 * Post type. 21 * 22 * @since 5.8.0 23 * @var string 24 */ 25 protected $post_type; 26 27 /** 28 * Constructor. 29 * 30 * @since 5.8.0 31 * 32 * @param string $post_type Post type. 33 */ 34 public function __construct( $post_type ) { 35 $this->post_type = $post_type; 36 $obj = get_post_type_object( $post_type ); 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 } 40 41 /** 42 * Registers the controllers routes. 43 * 44 * @since 5.8.0 45 * @since 6.1.0 Endpoint for fallback template content. 46 */ 47 public function register_routes() { 48 // Lists all templates. 49 register_rest_route( 50 $this->namespace, 51 '/' . $this->rest_base, 52 array( 53 array( 54 'methods' => WP_REST_Server::READABLE, 55 'callback' => array( $this, 'get_items' ), 56 'permission_callback' => array( $this, 'get_items_permissions_check' ), 57 'args' => $this->get_collection_params(), 58 ), 59 array( 60 'methods' => WP_REST_Server::CREATABLE, 61 'callback' => array( $this, 'create_item' ), 62 'permission_callback' => array( $this, 'create_item_permissions_check' ), 63 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 64 ), 65 'schema' => array( $this, 'get_public_item_schema' ), 66 ) 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 97 // Lists/updates a single template based on the given id. 98 register_rest_route( 99 $this->namespace, 100 // The route. 101 sprintf( 102 '/%s/(?P<id>%s%s)', 103 $this->rest_base, 104 /* 105 * Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`. 106 * Excludes invalid directory name characters: `/:<>*?"|`. 107 */ 108 '([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)', 109 // Matches the template name. 110 '[\/\w%-]+' 111 ), 112 array( 113 'args' => array( 114 'id' => array( 115 'description' => __( 'The id of a template' ), 116 'type' => 'string', 117 'sanitize_callback' => array( $this, '_sanitize_template_id' ), 118 ), 119 ), 120 array( 121 'methods' => WP_REST_Server::READABLE, 122 'callback' => array( $this, 'get_item' ), 123 'permission_callback' => array( $this, 'get_item_permissions_check' ), 124 'args' => array( 125 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 126 ), 127 ), 128 array( 129 'methods' => WP_REST_Server::EDITABLE, 130 'callback' => array( $this, 'update_item' ), 131 'permission_callback' => array( $this, 'update_item_permissions_check' ), 132 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 133 ), 134 array( 135 'methods' => WP_REST_Server::DELETABLE, 136 'callback' => array( $this, 'delete_item' ), 137 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 138 'args' => array( 139 'force' => array( 140 'type' => 'boolean', 141 'default' => false, 142 'description' => __( 'Whether to bypass Trash and force deletion.' ), 143 ), 144 ), 145 ), 146 'schema' => array( $this, 'get_public_item_schema' ), 147 ) 148 ); 149 } 150 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 $response = $this->prepare_item_for_response( $fallback_template, $request ); 169 170 return rest_ensure_response( $response ); 171 } 172 173 /** 174 * Checks if the user has permissions to make the request. 175 * 176 * @since 5.8.0 177 * 178 * @param WP_REST_Request $request Full details about the request. 179 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 180 */ 181 protected function permissions_check( $request ) { 182 /* 183 * Verify if the current user has edit_theme_options capability. 184 * This capability is required to edit/view/delete templates. 185 */ 186 if ( ! current_user_can( 'edit_theme_options' ) ) { 187 return new WP_Error( 188 'rest_cannot_manage_templates', 189 __( 'Sorry, you are not allowed to access the templates on this site.' ), 190 array( 191 'status' => rest_authorization_required_code(), 192 ) 193 ); 194 } 195 196 return true; 197 } 198 199 /** 200 * Requesting this endpoint for a template like 'twentytwentytwo//home' 201 * requires using a path like /wp/v2/templates/twentytwentytwo//home. There 202 * are special cases when WordPress routing corrects the name to contain 203 * only a single slash like 'twentytwentytwo/home'. 204 * 205 * This method doubles the last slash if it's not already doubled. It relies 206 * on the template ID format {theme_name}//{template_slug} and the fact that 207 * slugs cannot contain slashes. 208 * 209 * @since 5.9.0 210 * @see https://core.trac.wordpress.org/ticket/54507 211 * 212 * @param string $id Template ID. 213 * @return string Sanitized template ID. 214 */ 215 public function _sanitize_template_id( $id ) { 216 $id = urldecode( $id ); 217 218 $last_slash_pos = strrpos( $id, '/' ); 219 if ( false === $last_slash_pos ) { 220 return $id; 221 } 222 223 $is_double_slashed = substr( $id, $last_slash_pos - 1, 1 ) === '/'; 224 if ( $is_double_slashed ) { 225 return $id; 226 } 227 return ( 228 substr( $id, 0, $last_slash_pos ) 229 . '/' 230 . substr( $id, $last_slash_pos ) 231 ); 232 } 233 234 /** 235 * Checks if a given request has access to read templates. 236 * 237 * @since 5.8.0 238 * 239 * @param WP_REST_Request $request Full details about the request. 240 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 241 */ 242 public function get_items_permissions_check( $request ) { 243 return $this->permissions_check( $request ); 244 } 245 246 /** 247 * Returns a list of templates. 248 * 249 * @since 5.8.0 250 * 251 * @param WP_REST_Request $request The request instance. 252 * @return WP_REST_Response 253 */ 254 public function get_items( $request ) { 255 $query = array(); 256 if ( isset( $request['wp_id'] ) ) { 257 $query['wp_id'] = $request['wp_id']; 258 } 259 if ( isset( $request['area'] ) ) { 260 $query['area'] = $request['area']; 261 } 262 if ( isset( $request['post_type'] ) ) { 263 $query['post_type'] = $request['post_type']; 264 } 265 266 $templates = array(); 267 foreach ( get_block_templates( $query, $this->post_type ) as $template ) { 268 $data = $this->prepare_item_for_response( $template, $request ); 269 $templates[] = $this->prepare_response_for_collection( $data ); 270 } 271 272 return rest_ensure_response( $templates ); 273 } 274 275 /** 276 * Checks if a given request has access to read a single template. 277 * 278 * @since 5.8.0 279 * 280 * @param WP_REST_Request $request Full details about the request. 281 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. 282 */ 283 public function get_item_permissions_check( $request ) { 284 return $this->permissions_check( $request ); 285 } 286 287 /** 288 * Returns the given template 289 * 290 * @since 5.8.0 291 * 292 * @param WP_REST_Request $request The request instance. 293 * @return WP_REST_Response|WP_Error 294 */ 295 public function get_item( $request ) { 296 if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { 297 $template = get_block_file_template( $request['id'], $this->post_type ); 298 } else { 299 $template = get_block_template( $request['id'], $this->post_type ); 300 } 301 302 if ( ! $template ) { 303 return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); 304 } 305 306 return $this->prepare_item_for_response( $template, $request ); 307 } 308 309 /** 310 * Checks if a given request has access to write a single template. 311 * 312 * @since 5.8.0 313 * 314 * @param WP_REST_Request $request Full details about the request. 315 * @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise. 316 */ 317 public function update_item_permissions_check( $request ) { 318 return $this->permissions_check( $request ); 319 } 320 321 /** 322 * Updates a single template. 323 * 324 * @since 5.8.0 325 * 326 * @param WP_REST_Request $request Full details about the request. 327 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 328 */ 329 public function update_item( $request ) { 330 $template = get_block_template( $request['id'], $this->post_type ); 331 if ( ! $template ) { 332 return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); 333 } 334 335 $post_before = get_post( $template->wp_id ); 336 337 if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { 338 wp_delete_post( $template->wp_id, true ); 339 $request->set_param( 'context', 'edit' ); 340 341 $template = get_block_template( $request['id'], $this->post_type ); 342 $response = $this->prepare_item_for_response( $template, $request ); 343 344 return rest_ensure_response( $response ); 345 } 346 347 $changes = $this->prepare_item_for_database( $request ); 348 349 if ( is_wp_error( $changes ) ) { 350 return $changes; 351 } 352 353 if ( 'custom' === $template->source ) { 354 $update = true; 355 $result = wp_update_post( wp_slash( (array) $changes ), false ); 356 } else { 357 $update = false; 358 $post_before = null; 359 $result = wp_insert_post( wp_slash( (array) $changes ), false ); 360 } 361 362 if ( is_wp_error( $result ) ) { 363 if ( 'db_update_error' === $result->get_error_code() ) { 364 $result->add_data( array( 'status' => 500 ) ); 365 } else { 366 $result->add_data( array( 'status' => 400 ) ); 367 } 368 return $result; 369 } 370 371 $template = get_block_template( $request['id'], $this->post_type ); 372 $fields_update = $this->update_additional_fields_for_object( $template, $request ); 373 if ( is_wp_error( $fields_update ) ) { 374 return $fields_update; 375 } 376 377 $request->set_param( 'context', 'edit' ); 378 379 $post = get_post( $template->wp_id ); 380 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ 381 do_action( "rest_after_insert_{$this->post_type}", $post, $request, false ); 382 383 wp_after_insert_post( $post, $update, $post_before ); 384 385 $response = $this->prepare_item_for_response( $template, $request ); 386 387 return rest_ensure_response( $response ); 388 } 389 390 /** 391 * Checks if a given request has access to create a template. 392 * 393 * @since 5.8.0 394 * 395 * @param WP_REST_Request $request Full details about the request. 396 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. 397 */ 398 public function create_item_permissions_check( $request ) { 399 return $this->permissions_check( $request ); 400 } 401 402 /** 403 * Creates a single template. 404 * 405 * @since 5.8.0 406 * 407 * @param WP_REST_Request $request Full details about the request. 408 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 409 */ 410 public function create_item( $request ) { 411 $prepared_post = $this->prepare_item_for_database( $request ); 412 413 if ( is_wp_error( $prepared_post ) ) { 414 return $prepared_post; 415 } 416 417 $prepared_post->post_name = $request['slug']; 418 $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true ); 419 if ( is_wp_error( $post_id ) ) { 420 if ( 'db_insert_error' === $post_id->get_error_code() ) { 421 $post_id->add_data( array( 'status' => 500 ) ); 422 } else { 423 $post_id->add_data( array( 'status' => 400 ) ); 424 } 425 426 return $post_id; 427 } 428 $posts = get_block_templates( array( 'wp_id' => $post_id ), $this->post_type ); 429 if ( ! count( $posts ) ) { 430 return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ), array( 'status' => 400 ) ); 431 } 432 $id = $posts[0]->id; 433 $post = get_post( $post_id ); 434 $template = get_block_template( $id, $this->post_type ); 435 $fields_update = $this->update_additional_fields_for_object( $template, $request ); 436 if ( is_wp_error( $fields_update ) ) { 437 return $fields_update; 438 } 439 440 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ 441 do_action( "rest_after_insert_{$this->post_type}", $post, $request, true ); 442 443 wp_after_insert_post( $post, false, null ); 444 445 $response = $this->prepare_item_for_response( $template, $request ); 446 $response = rest_ensure_response( $response ); 447 448 $response->set_status( 201 ); 449 $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $template->id ) ) ); 450 451 return $response; 452 } 453 454 /** 455 * Checks if a given request has access to delete a single template. 456 * 457 * @since 5.8.0 458 * 459 * @param WP_REST_Request $request Full details about the request. 460 * @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise. 461 */ 462 public function delete_item_permissions_check( $request ) { 463 return $this->permissions_check( $request ); 464 } 465 466 /** 467 * Deletes a single template. 468 * 469 * @since 5.8.0 470 * 471 * @param WP_REST_Request $request Full details about the request. 472 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 473 */ 474 public function delete_item( $request ) { 475 $template = get_block_template( $request['id'], $this->post_type ); 476 if ( ! $template ) { 477 return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) ); 478 } 479 if ( 'custom' !== $template->source ) { 480 return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) ); 481 } 482 483 $id = $template->wp_id; 484 $force = (bool) $request['force']; 485 486 $request->set_param( 'context', 'edit' ); 487 488 // If we're forcing, then delete permanently. 489 if ( $force ) { 490 $previous = $this->prepare_item_for_response( $template, $request ); 491 $result = wp_delete_post( $id, true ); 492 $response = new WP_REST_Response(); 493 $response->set_data( 494 array( 495 'deleted' => true, 496 'previous' => $previous->get_data(), 497 ) 498 ); 499 } else { 500 // Otherwise, only trash if we haven't already. 501 if ( 'trash' === $template->status ) { 502 return new WP_Error( 503 'rest_template_already_trashed', 504 __( 'The template has already been deleted.' ), 505 array( 'status' => 410 ) 506 ); 507 } 508 509 /* 510 * (Note that internally this falls through to `wp_delete_post()` 511 * if the Trash is disabled.) 512 */ 513 $result = wp_trash_post( $id ); 514 $template->status = 'trash'; 515 $response = $this->prepare_item_for_response( $template, $request ); 516 } 517 518 if ( ! $result ) { 519 return new WP_Error( 520 'rest_cannot_delete', 521 __( 'The template cannot be deleted.' ), 522 array( 'status' => 500 ) 523 ); 524 } 525 526 return $response; 527 } 528 529 /** 530 * Prepares a single template for create or update. 531 * 532 * @since 5.8.0 533 * 534 * @param WP_REST_Request $request Request object. 535 * @return stdClass|WP_Error Changes to pass to wp_update_post. 536 */ 537 protected function prepare_item_for_database( $request ) { 538 $template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null; 539 $changes = new stdClass(); 540 if ( null === $template ) { 541 $changes->post_type = $this->post_type; 542 $changes->post_status = 'publish'; 543 $changes->tax_input = array( 544 'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : get_stylesheet(), 545 ); 546 } elseif ( 'custom' !== $template->source ) { 547 $changes->post_name = $template->slug; 548 $changes->post_type = $this->post_type; 549 $changes->post_status = 'publish'; 550 $changes->tax_input = array( 551 'wp_theme' => $template->theme, 552 ); 553 $changes->meta_input = array( 554 'origin' => $template->source, 555 ); 556 } else { 557 $changes->post_name = $template->slug; 558 $changes->ID = $template->wp_id; 559 $changes->post_status = 'publish'; 560 } 561 if ( isset( $request['content'] ) ) { 562 if ( is_string( $request['content'] ) ) { 563 $changes->post_content = $request['content']; 564 } elseif ( isset( $request['content']['raw'] ) ) { 565 $changes->post_content = $request['content']['raw']; 566 } 567 } elseif ( null !== $template && 'custom' !== $template->source ) { 568 $changes->post_content = $template->content; 569 } 570 if ( isset( $request['title'] ) ) { 571 if ( is_string( $request['title'] ) ) { 572 $changes->post_title = $request['title']; 573 } elseif ( ! empty( $request['title']['raw'] ) ) { 574 $changes->post_title = $request['title']['raw']; 575 } 576 } elseif ( null !== $template && 'custom' !== $template->source ) { 577 $changes->post_title = $template->title; 578 } 579 if ( isset( $request['description'] ) ) { 580 $changes->post_excerpt = $request['description']; 581 } elseif ( null !== $template && 'custom' !== $template->source ) { 582 $changes->post_excerpt = $template->description; 583 } 584 585 if ( 'wp_template' === $this->post_type && isset( $request['is_wp_suggestion'] ) ) { 586 $changes->meta_input = wp_parse_args( 587 array( 588 'is_wp_suggestion' => $request['is_wp_suggestion'], 589 ), 590 $changes->meta_input = array() 591 ); 592 } 593 594 if ( 'wp_template_part' === $this->post_type ) { 595 if ( isset( $request['area'] ) ) { 596 $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $request['area'] ); 597 } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { 598 $changes->tax_input['wp_template_part_area'] = _filter_block_template_part_area( $template->area ); 599 } elseif ( empty( $template->area ) ) { 600 $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; 601 } 602 } 603 604 if ( ! empty( $request['author'] ) ) { 605 $post_author = (int) $request['author']; 606 607 if ( get_current_user_id() !== $post_author ) { 608 $user_obj = get_userdata( $post_author ); 609 610 if ( ! $user_obj ) { 611 return new WP_Error( 612 'rest_invalid_author', 613 __( 'Invalid author ID.' ), 614 array( 'status' => 400 ) 615 ); 616 } 617 } 618 619 $changes->post_author = $post_author; 620 } 621 622 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ 623 return apply_filters( "rest_pre_insert_{$this->post_type}", $changes, $request ); 624 } 625 626 /** 627 * Prepare a single template output for response 628 * 629 * @since 5.8.0 630 * @since 5.9.0 Renamed `$template` to `$item` to match parent class for PHP 8 named parameter support. 631 * @since 6.3.0 Added `modified` property to the response. 632 * 633 * @param WP_Block_Template $item Template instance. 634 * @param WP_REST_Request $request Request object. 635 * @return WP_REST_Response Response object. 636 */ 637 public function prepare_item_for_response( $item, $request ) { 638 // Restores the more descriptive, specific name for use within this method. 639 $template = $item; 640 641 $fields = $this->get_fields_for_response( $request ); 642 643 // Base fields for every template. 644 $data = array(); 645 646 if ( rest_is_field_included( 'id', $fields ) ) { 647 $data['id'] = $template->id; 648 } 649 650 if ( rest_is_field_included( 'theme', $fields ) ) { 651 $data['theme'] = $template->theme; 652 } 653 654 if ( rest_is_field_included( 'content', $fields ) ) { 655 $data['content'] = array(); 656 } 657 if ( rest_is_field_included( 'content.raw', $fields ) ) { 658 $data['content']['raw'] = $template->content; 659 } 660 661 if ( rest_is_field_included( 'content.block_version', $fields ) ) { 662 $data['content']['block_version'] = block_version( $template->content ); 663 } 664 665 if ( rest_is_field_included( 'slug', $fields ) ) { 666 $data['slug'] = $template->slug; 667 } 668 669 if ( rest_is_field_included( 'source', $fields ) ) { 670 $data['source'] = $template->source; 671 } 672 673 if ( rest_is_field_included( 'origin', $fields ) ) { 674 $data['origin'] = $template->origin; 675 } 676 677 if ( rest_is_field_included( 'type', $fields ) ) { 678 $data['type'] = $template->type; 679 } 680 681 if ( rest_is_field_included( 'description', $fields ) ) { 682 $data['description'] = $template->description; 683 } 684 685 if ( rest_is_field_included( 'title', $fields ) ) { 686 $data['title'] = array(); 687 } 688 689 if ( rest_is_field_included( 'title.raw', $fields ) ) { 690 $data['title']['raw'] = $template->title; 691 } 692 693 if ( rest_is_field_included( 'title.rendered', $fields ) ) { 694 if ( $template->wp_id ) { 695 /** This filter is documented in wp-includes/post-template.php */ 696 $data['title']['rendered'] = apply_filters( 'the_title', $template->title, $template->wp_id ); 697 } else { 698 $data['title']['rendered'] = $template->title; 699 } 700 } 701 702 if ( rest_is_field_included( 'status', $fields ) ) { 703 $data['status'] = $template->status; 704 } 705 706 if ( rest_is_field_included( 'wp_id', $fields ) ) { 707 $data['wp_id'] = (int) $template->wp_id; 708 } 709 710 if ( rest_is_field_included( 'has_theme_file', $fields ) ) { 711 $data['has_theme_file'] = (bool) $template->has_theme_file; 712 } 713 714 if ( rest_is_field_included( 'is_custom', $fields ) && 'wp_template' === $template->type ) { 715 $data['is_custom'] = $template->is_custom; 716 } 717 718 if ( rest_is_field_included( 'author', $fields ) ) { 719 $data['author'] = (int) $template->author; 720 } 721 722 if ( rest_is_field_included( 'area', $fields ) && 'wp_template_part' === $template->type ) { 723 $data['area'] = $template->area; 724 } 725 726 if ( rest_is_field_included( 'modified', $fields ) ) { 727 $data['modified'] = mysql_to_rfc3339( $template->modified ); 728 } 729 730 if ( rest_is_field_included( 'author_text', $fields ) ) { 731 $data['author_text'] = self::get_wp_templates_author_text_field( $template ); 732 } 733 734 if ( rest_is_field_included( 'original_source', $fields ) ) { 735 $data['original_source'] = self::get_wp_templates_original_source_field( $template ); 736 } 737 738 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 739 $data = $this->add_additional_fields_to_object( $data, $request ); 740 $data = $this->filter_response_by_context( $data, $context ); 741 742 // Wrap the data in a response object. 743 $response = rest_ensure_response( $data ); 744 745 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 746 $links = $this->prepare_links( $template->id ); 747 $response->add_links( $links ); 748 if ( ! empty( $links['self']['href'] ) ) { 749 $actions = $this->get_available_actions(); 750 $self = $links['self']['href']; 751 foreach ( $actions as $rel ) { 752 $response->add_link( $rel, $self ); 753 } 754 } 755 } 756 757 return $response; 758 } 759 760 /** 761 * Returns the source from where the template originally comes from. 762 * 763 * @since 6.5.0 764 * 765 * @param WP_Block_Template $template_object Template instance. 766 * @return string Original source of the template one of theme, plugin, site, or user. 767 */ 768 private static function get_wp_templates_original_source_field( $template_object ) { 769 if ( 'wp_template' === $template_object->type || 'wp_template_part' === $template_object->type ) { 770 // Added by theme. 771 // Template originally provided by a theme, but customized by a user. 772 // Templates originally didn't have the 'origin' field so identify 773 // older customized templates by checking for no origin and a 'theme' 774 // or 'custom' source. 775 if ( $template_object->has_theme_file && 776 ( 'theme' === $template_object->origin || ( 777 empty( $template_object->origin ) && in_array( 778 $template_object->source, 779 array( 780 'theme', 781 'custom', 782 ), 783 true 784 ) ) 785 ) 786 ) { 787 return 'theme'; 788 } 789 790 // Added by plugin. 791 if ( $template_object->has_theme_file && 'plugin' === $template_object->origin ) { 792 return 'plugin'; 793 } 794 795 // Added by site. 796 // Template was created from scratch, but has no author. Author support 797 // was only added to templates in WordPress 5.9. Fallback to showing the 798 // site logo and title. 799 if ( empty( $template_object->has_theme_file ) && 'custom' === $template_object->source && empty( $template_object->author ) ) { 800 return 'site'; 801 } 802 } 803 804 // Added by user. 805 return 'user'; 806 } 807 808 /** 809 * Returns a human readable text for the author of the template. 810 * 811 * @since 6.5.0 812 * 813 * @param WP_Block_Template $template_object Template instance. 814 * @return string Human readable text for the author. 815 */ 816 private static function get_wp_templates_author_text_field( $template_object ) { 817 $original_source = self::get_wp_templates_original_source_field( $template_object ); 818 switch ( $original_source ) { 819 case 'theme': 820 $theme_name = wp_get_theme( $template_object->theme )->get( 'Name' ); 821 return empty( $theme_name ) ? $template_object->theme : $theme_name; 822 case 'plugin': 823 $plugins = get_plugins(); 824 $plugin = $plugins[ plugin_basename( sanitize_text_field( $template_object->theme . '.php' ) ) ]; 825 return empty( $plugin['Name'] ) ? $template_object->theme : $plugin['Name']; 826 case 'site': 827 return get_bloginfo( 'name' ); 828 case 'user': 829 $author = get_user_by( 'id', $template_object->author ); 830 if ( ! $author ) { 831 return __( 'Unknown author' ); 832 } 833 return $author->get( 'display_name' ); 834 } 835 } 836 837 838 /** 839 * Prepares links for the request. 840 * 841 * @since 5.8.0 842 * 843 * @param integer $id ID. 844 * @return array Links for the given post. 845 */ 846 protected function prepare_links( $id ) { 847 $links = array( 848 'self' => array( 849 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $id ) ), 850 ), 851 'collection' => array( 852 'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ), 853 ), 854 'about' => array( 855 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), 856 ), 857 ); 858 859 if ( post_type_supports( $this->post_type, 'revisions' ) ) { 860 $template = get_block_template( $id, $this->post_type ); 861 if ( $template instanceof WP_Block_Template && ! empty( $template->wp_id ) ) { 862 $revisions = wp_get_latest_revision_id_and_total_count( $template->wp_id ); 863 $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; 864 $revisions_base = sprintf( '/%s/%s/%s/revisions', $this->namespace, $this->rest_base, $id ); 865 866 $links['version-history'] = array( 867 'href' => rest_url( $revisions_base ), 868 'count' => $revisions_count, 869 ); 870 871 if ( $revisions_count > 0 ) { 872 $links['predecessor-version'] = array( 873 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), 874 'id' => $revisions['latest_id'], 875 ); 876 } 877 } 878 } 879 880 return $links; 881 } 882 883 /** 884 * Get the link relations available for the post and current user. 885 * 886 * @since 5.8.0 887 * 888 * @return string[] List of link relations. 889 */ 890 protected function get_available_actions() { 891 $rels = array(); 892 893 $post_type = get_post_type_object( $this->post_type ); 894 895 if ( current_user_can( $post_type->cap->publish_posts ) ) { 896 $rels[] = 'https://api.w.org/action-publish'; 897 } 898 899 if ( current_user_can( 'unfiltered_html' ) ) { 900 $rels[] = 'https://api.w.org/action-unfiltered-html'; 901 } 902 903 return $rels; 904 } 905 906 /** 907 * Retrieves the query params for the posts collection. 908 * 909 * @since 5.8.0 910 * @since 5.9.0 Added `'area'` and `'post_type'`. 911 * 912 * @return array Collection parameters. 913 */ 914 public function get_collection_params() { 915 return array( 916 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 917 'wp_id' => array( 918 'description' => __( 'Limit to the specified post id.' ), 919 'type' => 'integer', 920 ), 921 'area' => array( 922 'description' => __( 'Limit to the specified template part area.' ), 923 'type' => 'string', 924 ), 925 'post_type' => array( 926 'description' => __( 'Post type to get the templates for.' ), 927 'type' => 'string', 928 ), 929 ); 930 } 931 932 /** 933 * Retrieves the block type' schema, conforming to JSON Schema. 934 * 935 * @since 5.8.0 936 * @since 5.9.0 Added `'area'`. 937 * 938 * @return array Item schema data. 939 */ 940 public function get_item_schema() { 941 if ( $this->schema ) { 942 return $this->add_additional_fields_schema( $this->schema ); 943 } 944 945 $schema = array( 946 '$schema' => 'http://json-schema.org/draft-04/schema#', 947 'title' => $this->post_type, 948 'type' => 'object', 949 'properties' => array( 950 'id' => array( 951 'description' => __( 'ID of template.' ), 952 'type' => 'string', 953 'context' => array( 'embed', 'view', 'edit' ), 954 'readonly' => true, 955 ), 956 'slug' => array( 957 'description' => __( 'Unique slug identifying the template.' ), 958 'type' => 'string', 959 'context' => array( 'embed', 'view', 'edit' ), 960 'required' => true, 961 'minLength' => 1, 962 'pattern' => '[a-zA-Z0-9_\%-]+', 963 ), 964 'theme' => array( 965 'description' => __( 'Theme identifier for the template.' ), 966 'type' => 'string', 967 'context' => array( 'embed', 'view', 'edit' ), 968 ), 969 'type' => array( 970 'description' => __( 'Type of template.' ), 971 'type' => 'string', 972 'context' => array( 'embed', 'view', 'edit' ), 973 ), 974 'source' => array( 975 'description' => __( 'Source of template' ), 976 'type' => 'string', 977 'context' => array( 'embed', 'view', 'edit' ), 978 'readonly' => true, 979 ), 980 'origin' => array( 981 'description' => __( 'Source of a customized template' ), 982 'type' => 'string', 983 'context' => array( 'embed', 'view', 'edit' ), 984 'readonly' => true, 985 ), 986 'content' => array( 987 'description' => __( 'Content of template.' ), 988 'type' => array( 'object', 'string' ), 989 'default' => '', 990 'context' => array( 'embed', 'view', 'edit' ), 991 'properties' => array( 992 'raw' => array( 993 'description' => __( 'Content for the template, as it exists in the database.' ), 994 'type' => 'string', 995 'context' => array( 'view', 'edit' ), 996 ), 997 'block_version' => array( 998 'description' => __( 'Version of the content block format used by the template.' ), 999 'type' => 'integer', 1000 'context' => array( 'edit' ), 1001 'readonly' => true, 1002 ), 1003 ), 1004 ), 1005 'title' => array( 1006 'description' => __( 'Title of template.' ), 1007 'type' => array( 'object', 'string' ), 1008 'default' => '', 1009 'context' => array( 'embed', 'view', 'edit' ), 1010 'properties' => array( 1011 'raw' => array( 1012 'description' => __( 'Title for the template, as it exists in the database.' ), 1013 'type' => 'string', 1014 'context' => array( 'view', 'edit', 'embed' ), 1015 ), 1016 'rendered' => array( 1017 'description' => __( 'HTML title for the template, transformed for display.' ), 1018 'type' => 'string', 1019 'context' => array( 'view', 'edit', 'embed' ), 1020 'readonly' => true, 1021 ), 1022 ), 1023 ), 1024 'description' => array( 1025 'description' => __( 'Description of template.' ), 1026 'type' => 'string', 1027 'default' => '', 1028 'context' => array( 'embed', 'view', 'edit' ), 1029 ), 1030 'status' => array( 1031 'description' => __( 'Status of template.' ), 1032 'type' => 'string', 1033 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 1034 'default' => 'publish', 1035 'context' => array( 'embed', 'view', 'edit' ), 1036 ), 1037 'wp_id' => array( 1038 'description' => __( 'Post ID.' ), 1039 'type' => 'integer', 1040 'context' => array( 'embed', 'view', 'edit' ), 1041 'readonly' => true, 1042 ), 1043 'has_theme_file' => array( 1044 'description' => __( 'Theme file exists.' ), 1045 'type' => 'bool', 1046 'context' => array( 'embed', 'view', 'edit' ), 1047 'readonly' => true, 1048 ), 1049 'author' => array( 1050 'description' => __( 'The ID for the author of the template.' ), 1051 'type' => 'integer', 1052 'context' => array( 'view', 'edit', 'embed' ), 1053 ), 1054 'modified' => array( 1055 'description' => __( "The date the template was last modified, in the site's timezone." ), 1056 'type' => 'string', 1057 'format' => 'date-time', 1058 'context' => array( 'view', 'edit' ), 1059 'readonly' => true, 1060 ), 1061 'author_text' => array( 1062 'type' => 'string', 1063 'description' => __( 'Human readable text for the author.' ), 1064 'readonly' => true, 1065 'context' => array( 'view', 'edit', 'embed' ), 1066 ), 1067 'original_source' => array( 1068 'description' => __( 'Where the template originally comes from e.g. \'theme\'' ), 1069 'type' => 'string', 1070 'readonly' => true, 1071 'context' => array( 'view', 'edit', 'embed' ), 1072 'enum' => array( 1073 'theme', 1074 'plugin', 1075 'site', 1076 'user', 1077 ), 1078 ), 1079 ), 1080 ); 1081 1082 if ( 'wp_template' === $this->post_type ) { 1083 $schema['properties']['is_custom'] = array( 1084 'description' => __( 'Whether a template is a custom template.' ), 1085 'type' => 'bool', 1086 'context' => array( 'embed', 'view', 'edit' ), 1087 'readonly' => true, 1088 ); 1089 } 1090 1091 if ( 'wp_template_part' === $this->post_type ) { 1092 $schema['properties']['area'] = array( 1093 'description' => __( 'Where the template part is intended for use (header, footer, etc.)' ), 1094 'type' => 'string', 1095 'context' => array( 'embed', 'view', 'edit' ), 1096 ); 1097 } 1098 1099 $this->schema = $schema; 1100 1101 return $this->add_additional_fields_schema( $this->schema ); 1102 } 1103 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Apr 26 08:20:02 2024 | Cross-referenced by PHPXref |