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