[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Themes_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.0.0 8 */ 9 10 /** 11 * Core class used to manage themes via the REST API. 12 * 13 * @since 5.0.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Themes_Controller extends WP_REST_Controller { 18 19 /** 20 * Constructor. 21 * 22 * @since 5.0.0 23 */ 24 public function __construct() { 25 $this->namespace = 'wp/v2'; 26 $this->rest_base = 'themes'; 27 } 28 29 /** 30 * Registers the routes for the objects of the controller. 31 * 32 * @since 5.0.0 33 * 34 * @see register_rest_route() 35 */ 36 public function register_routes() { 37 register_rest_route( 38 $this->namespace, 39 '/' . $this->rest_base, 40 array( 41 array( 42 'methods' => WP_REST_Server::READABLE, 43 'callback' => array( $this, 'get_items' ), 44 'permission_callback' => array( $this, 'get_items_permissions_check' ), 45 'args' => $this->get_collection_params(), 46 ), 47 'schema' => array( $this, 'get_item_schema' ), 48 ) 49 ); 50 51 register_rest_route( 52 $this->namespace, 53 '/' . $this->rest_base . '/(?P<stylesheet>[\w-]+)', 54 array( 55 'args' => array( 56 'stylesheet' => array( 57 'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ), 58 'type' => 'string', 59 ), 60 ), 61 array( 62 'methods' => WP_REST_Server::READABLE, 63 'callback' => array( $this, 'get_item' ), 64 'permission_callback' => array( $this, 'get_item_permissions_check' ), 65 ), 66 'schema' => array( $this, 'get_public_item_schema' ), 67 ) 68 ); 69 } 70 71 /** 72 * Checks if a given request has access to read the theme. 73 * 74 * @since 5.0.0 75 * 76 * @param WP_REST_Request $request Full details about the request. 77 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. 78 */ 79 public function get_items_permissions_check( $request ) { 80 if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { 81 return true; 82 } 83 84 $registered = $this->get_collection_params(); 85 if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) { 86 return $this->check_read_active_theme_permission(); 87 } 88 89 return new WP_Error( 90 'rest_cannot_view_themes', 91 __( 'Sorry, you are not allowed to view themes.' ), 92 array( 'status' => rest_authorization_required_code() ) 93 ); 94 } 95 96 /** 97 * Checks if a given request has access to read the theme. 98 * 99 * @since 5.7.0 100 * 101 * @param WP_REST_Request $request Full details about the request. 102 * @return bool|WP_Error True if the request has read access for the item, otherwise WP_Error object. 103 */ 104 public function get_item_permissions_check( $request ) { 105 if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) { 106 return true; 107 } 108 109 $wp_theme = wp_get_theme( $request['stylesheet'] ); 110 $current_theme = wp_get_theme(); 111 112 if ( $this->is_same_theme( $wp_theme, $current_theme ) ) { 113 return $this->check_read_active_theme_permission(); 114 } 115 116 return new WP_Error( 117 'rest_cannot_view_themes', 118 __( 'Sorry, you are not allowed to view themes.' ), 119 array( 'status' => rest_authorization_required_code() ) 120 ); 121 } 122 123 /** 124 * Checks if a theme can be read. 125 * 126 * @since 5.7.0 127 * 128 * @return bool|WP_Error Whether the theme can be read. 129 */ 130 protected function check_read_active_theme_permission() { 131 if ( current_user_can( 'edit_posts' ) ) { 132 return true; 133 } 134 135 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 136 if ( current_user_can( $post_type->cap->edit_posts ) ) { 137 return true; 138 } 139 } 140 141 return new WP_Error( 142 'rest_cannot_view_active_theme', 143 __( 'Sorry, you are not allowed to view the active theme.' ), 144 array( 'status' => rest_authorization_required_code() ) 145 ); 146 } 147 148 /** 149 * Retrieves a single theme. 150 * 151 * @since 5.7.0 152 * 153 * @param WP_REST_Request $request Full details about the request. 154 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 155 */ 156 public function get_item( $request ) { 157 $wp_theme = wp_get_theme( $request['stylesheet'] ); 158 if ( ! $wp_theme->exists() ) { 159 return new WP_Error( 160 'rest_theme_not_found', 161 __( 'Theme not found.' ), 162 array( 'status' => 404 ) 163 ); 164 } 165 $data = $this->prepare_item_for_response( $wp_theme, $request ); 166 167 return rest_ensure_response( $data ); 168 } 169 170 /** 171 * Retrieves a collection of themes. 172 * 173 * @since 5.0.0 174 * 175 * @param WP_REST_Request $request Full details about the request. 176 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 177 */ 178 public function get_items( $request ) { 179 $themes = array(); 180 181 $active_themes = wp_get_themes(); 182 $current_theme = wp_get_theme(); 183 $status = $request['status']; 184 185 foreach ( $active_themes as $theme_name => $theme ) { 186 $theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; 187 if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) { 188 continue; 189 } 190 191 $prepared = $this->prepare_item_for_response( $theme, $request ); 192 $themes[] = $this->prepare_response_for_collection( $prepared ); 193 } 194 195 $response = rest_ensure_response( $themes ); 196 197 $response->header( 'X-WP-Total', count( $themes ) ); 198 $response->header( 'X-WP-TotalPages', 1 ); 199 200 return $response; 201 } 202 203 /** 204 * Prepares a single theme output for response. 205 * 206 * @since 5.0.0 207 * 208 * @param WP_Theme $theme Theme object. 209 * @param WP_REST_Request $request Request object. 210 * @return WP_REST_Response Response object. 211 */ 212 public function prepare_item_for_response( $theme, $request ) { 213 $data = array(); 214 $fields = $this->get_fields_for_response( $request ); 215 216 if ( rest_is_field_included( 'stylesheet', $fields ) ) { 217 $data['stylesheet'] = $theme->get_stylesheet(); 218 } 219 220 if ( rest_is_field_included( 'template', $fields ) ) { 221 /** 222 * Use the get_template() method, not the 'Template' header, for finding the template. 223 * The 'Template' header is only good for what was written in the style.css, while 224 * get_template() takes into account where WordPress actually located the theme and 225 * whether it is actually valid. 226 */ 227 $data['template'] = $theme->get_template(); 228 } 229 230 $plain_field_mappings = array( 231 'requires_php' => 'RequiresPHP', 232 'requires_wp' => 'RequiresWP', 233 'textdomain' => 'TextDomain', 234 'version' => 'Version', 235 ); 236 237 foreach ( $plain_field_mappings as $field => $header ) { 238 if ( rest_is_field_included( $field, $fields ) ) { 239 $data[ $field ] = $theme->get( $header ); 240 } 241 } 242 243 if ( rest_is_field_included( 'screenshot', $fields ) ) { 244 // Using $theme->get_screenshot() with no args to get absolute URL. 245 $data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : ''; 246 } 247 248 $rich_field_mappings = array( 249 'author' => 'Author', 250 'author_uri' => 'AuthorURI', 251 'description' => 'Description', 252 'name' => 'Name', 253 'tags' => 'Tags', 254 'theme_uri' => 'ThemeURI', 255 ); 256 257 foreach ( $rich_field_mappings as $field => $header ) { 258 if ( rest_is_field_included( "{$field}.raw", $fields ) ) { 259 $data[ $field ]['raw'] = $theme->display( $header, false, true ); 260 } 261 262 if ( rest_is_field_included( "{$field}.rendered", $fields ) ) { 263 $data[ $field ]['rendered'] = $theme->display( $header ); 264 } 265 } 266 267 $current_theme = wp_get_theme(); 268 if ( rest_is_field_included( 'status', $fields ) ) { 269 $data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive'; 270 } 271 272 if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) { 273 foreach ( get_registered_theme_features() as $feature => $config ) { 274 if ( ! is_array( $config['show_in_rest'] ) ) { 275 continue; 276 } 277 278 $name = $config['show_in_rest']['name']; 279 280 if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) { 281 continue; 282 } 283 284 if ( ! current_theme_supports( $feature ) ) { 285 $data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default']; 286 continue; 287 } 288 289 $support = get_theme_support( $feature ); 290 291 if ( isset( $config['show_in_rest']['prepare_callback'] ) ) { 292 $prepare = $config['show_in_rest']['prepare_callback']; 293 } else { 294 $prepare = array( $this, 'prepare_theme_support' ); 295 } 296 297 $prepared = $prepare( $support, $config, $feature, $request ); 298 299 if ( is_wp_error( $prepared ) ) { 300 continue; 301 } 302 303 $data['theme_supports'][ $name ] = $prepared; 304 } 305 } 306 307 $data = $this->add_additional_fields_to_object( $data, $request ); 308 309 // Wrap the data in a response object. 310 $response = rest_ensure_response( $data ); 311 312 $response->add_links( $this->prepare_links( $theme ) ); 313 314 /** 315 * Filters theme data returned from the REST API. 316 * 317 * @since 5.0.0 318 * 319 * @param WP_REST_Response $response The response object. 320 * @param WP_Theme $theme Theme object used to create response. 321 * @param WP_REST_Request $request Request object. 322 */ 323 return apply_filters( 'rest_prepare_theme', $response, $theme, $request ); 324 } 325 326 /** 327 * Prepares links for the request. 328 * 329 * @since 5.7.0 330 * 331 * @param WP_Theme $theme Theme data. 332 * @return array Links for the given block type. 333 */ 334 protected function prepare_links( $theme ) { 335 return array( 336 'self' => array( 337 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ), 338 ), 339 'collection' => array( 340 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 341 ), 342 ); 343 } 344 345 /** 346 * Helper function to compare two themes. 347 * 348 * @since 5.7.0 349 * 350 * @param WP_Theme $theme_a First theme to compare. 351 * @param WP_Theme $theme_b Second theme to compare. 352 * 353 * @return bool 354 */ 355 protected function is_same_theme( $theme_a, $theme_b ) { 356 return $theme_a->get_stylesheet() === $theme_b->get_stylesheet(); 357 } 358 359 /** 360 * Prepares the theme support value for inclusion in the REST API response. 361 * 362 * @since 5.5.0 363 * 364 * @param mixed $support The raw value from get_theme_support(). 365 * @param array $args The feature's registration args. 366 * @param string $feature The feature name. 367 * @param WP_REST_Request $request The request object. 368 * @return mixed The prepared support value. 369 */ 370 protected function prepare_theme_support( $support, $args, $feature, $request ) { 371 $schema = $args['show_in_rest']['schema']; 372 373 if ( 'boolean' === $schema['type'] ) { 374 return true; 375 } 376 377 if ( is_array( $support ) && ! $args['variadic'] ) { 378 $support = $support[0]; 379 } 380 381 return rest_sanitize_value_from_schema( $support, $schema ); 382 } 383 384 /** 385 * Retrieves the theme's schema, conforming to JSON Schema. 386 * 387 * @since 5.0.0 388 * 389 * @return array Item schema data. 390 */ 391 public function get_item_schema() { 392 if ( $this->schema ) { 393 return $this->add_additional_fields_schema( $this->schema ); 394 } 395 396 $schema = array( 397 '$schema' => 'http://json-schema.org/draft-04/schema#', 398 'title' => 'theme', 399 'type' => 'object', 400 'properties' => array( 401 'stylesheet' => array( 402 'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ), 403 'type' => 'string', 404 'readonly' => true, 405 ), 406 'template' => array( 407 'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ), 408 'type' => 'string', 409 'readonly' => true, 410 ), 411 'author' => array( 412 'description' => __( 'The theme author.' ), 413 'type' => 'object', 414 'readonly' => true, 415 'properties' => array( 416 'raw' => array( 417 'description' => __( 'The theme author\'s name, as found in the theme header.' ), 418 'type' => 'string', 419 ), 420 'rendered' => array( 421 'description' => __( 'HTML for the theme author, transformed for display.' ), 422 'type' => 'string', 423 ), 424 ), 425 ), 426 'author_uri' => array( 427 'description' => __( 'The website of the theme author.' ), 428 'type' => 'object', 429 'readonly' => true, 430 'properties' => array( 431 'raw' => array( 432 'description' => __( 'The website of the theme author, as found in the theme header.' ), 433 'type' => 'string', 434 'format' => 'uri', 435 ), 436 'rendered' => array( 437 'description' => __( 'The website of the theme author, transformed for display.' ), 438 'type' => 'string', 439 'format' => 'uri', 440 ), 441 ), 442 ), 443 'description' => array( 444 'description' => __( 'A description of the theme.' ), 445 'type' => 'object', 446 'readonly' => true, 447 'properties' => array( 448 'raw' => array( 449 'description' => __( 'The theme description, as found in the theme header.' ), 450 'type' => 'string', 451 ), 452 'rendered' => array( 453 'description' => __( 'The theme description, transformed for display.' ), 454 'type' => 'string', 455 ), 456 ), 457 ), 458 'name' => array( 459 'description' => __( 'The name of the theme.' ), 460 'type' => 'object', 461 'readonly' => true, 462 'properties' => array( 463 'raw' => array( 464 'description' => __( 'The theme name, as found in the theme header.' ), 465 'type' => 'string', 466 ), 467 'rendered' => array( 468 'description' => __( 'The theme name, transformed for display.' ), 469 'type' => 'string', 470 ), 471 ), 472 ), 473 'requires_php' => array( 474 'description' => __( 'The minimum PHP version required for the theme to work.' ), 475 'type' => 'string', 476 'readonly' => true, 477 ), 478 'requires_wp' => array( 479 'description' => __( 'The minimum WordPress version required for the theme to work.' ), 480 'type' => 'string', 481 'readonly' => true, 482 ), 483 'screenshot' => array( 484 'description' => __( 'The theme\'s screenshot URL.' ), 485 'type' => 'string', 486 'format' => 'uri', 487 'readonly' => true, 488 ), 489 'tags' => array( 490 'description' => __( 'Tags indicating styles and features of the theme.' ), 491 'type' => 'object', 492 'readonly' => true, 493 'properties' => array( 494 'raw' => array( 495 'description' => __( 'The theme tags, as found in the theme header.' ), 496 'type' => 'array', 497 'items' => array( 498 'type' => 'string', 499 ), 500 ), 501 'rendered' => array( 502 'description' => __( 'The theme tags, transformed for display.' ), 503 'type' => 'string', 504 ), 505 ), 506 ), 507 'textdomain' => array( 508 'description' => __( 'The theme\'s text domain.' ), 509 'type' => 'string', 510 'readonly' => true, 511 ), 512 'theme_supports' => array( 513 'description' => __( 'Features supported by this theme.' ), 514 'type' => 'object', 515 'readonly' => true, 516 'properties' => array(), 517 ), 518 'theme_uri' => array( 519 'description' => __( 'The URI of the theme\'s webpage.' ), 520 'type' => 'object', 521 'readonly' => true, 522 'properties' => array( 523 'raw' => array( 524 'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ), 525 'type' => 'string', 526 'format' => 'uri', 527 ), 528 'rendered' => array( 529 'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ), 530 'type' => 'string', 531 'format' => 'uri', 532 ), 533 ), 534 ), 535 'version' => array( 536 'description' => __( 'The theme\'s current version.' ), 537 'type' => 'string', 538 'readonly' => true, 539 ), 540 'status' => array( 541 'description' => __( 'A named status for the theme.' ), 542 'type' => 'string', 543 'enum' => array( 'inactive', 'active' ), 544 ), 545 ), 546 ); 547 548 foreach ( get_registered_theme_features() as $feature => $config ) { 549 if ( ! is_array( $config['show_in_rest'] ) ) { 550 continue; 551 } 552 553 $name = $config['show_in_rest']['name']; 554 555 $schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema']; 556 } 557 558 $this->schema = $schema; 559 560 return $this->add_additional_fields_schema( $this->schema ); 561 } 562 563 /** 564 * Retrieves the search params for the themes collection. 565 * 566 * @since 5.0.0 567 * 568 * @return array Collection parameters. 569 */ 570 public function get_collection_params() { 571 $query_params = array( 572 'status' => array( 573 'description' => __( 'Limit result set to themes assigned one or more statuses.' ), 574 'type' => 'array', 575 'items' => array( 576 'enum' => array( 'active', 'inactive' ), 577 'type' => 'string', 578 ), 579 ), 580 ); 581 582 /** 583 * Filters REST API collection parameters for the themes controller. 584 * 585 * @since 5.0.0 586 * 587 * @param array $query_params JSON Schema-formatted collection parameters. 588 */ 589 return apply_filters( 'rest_themes_collection_params', $query_params ); 590 } 591 592 /** 593 * Sanitizes and validates the list of theme status. 594 * 595 * @since 5.0.0 596 * @deprecated 5.7.0 597 * 598 * @param string|array $statuses One or more theme statuses. 599 * @param WP_REST_Request $request Full details about the request. 600 * @param string $parameter Additional parameter to pass to validation. 601 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. 602 */ 603 public function sanitize_theme_status( $statuses, $request, $parameter ) { 604 _deprecated_function( __METHOD__, '5.7.0' ); 605 606 $statuses = wp_parse_slug_list( $statuses ); 607 608 foreach ( $statuses as $status ) { 609 $result = rest_validate_request_arg( $status, $request, $parameter ); 610 611 if ( is_wp_error( $result ) ) { 612 return $result; 613 } 614 } 615 616 return $statuses; 617 } 618 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Tue Jan 26 08:20:01 2021 | Cross-referenced by PHPXref |