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