[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Widget_Types_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.8.0 8 */ 9 10 /** 11 * Core class to access widget types via the REST API. 12 * 13 * @since 5.8.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Widget_Types_Controller extends WP_REST_Controller { 18 19 /** 20 * Constructor. 21 * 22 * @since 5.8.0 23 */ 24 public function __construct() { 25 $this->namespace = 'wp/v2'; 26 $this->rest_base = 'widget-types'; 27 } 28 29 /** 30 * Registers the widget type routes. 31 * 32 * @since 5.8.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_public_item_schema' ), 48 ) 49 ); 50 51 register_rest_route( 52 $this->namespace, 53 '/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)', 54 array( 55 'args' => array( 56 'id' => array( 57 'description' => __( 'The widget type id.' ), 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 'args' => $this->get_collection_params(), 66 ), 67 'schema' => array( $this, 'get_public_item_schema' ), 68 ) 69 ); 70 71 register_rest_route( 72 $this->namespace, 73 '/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)/encode', 74 array( 75 'args' => array( 76 'id' => array( 77 'description' => __( 'The widget type id.' ), 78 'type' => 'string', 79 'required' => true, 80 ), 81 'instance' => array( 82 'description' => __( 'Current instance settings of the widget.' ), 83 'type' => 'object', 84 ), 85 'form_data' => array( 86 'description' => __( 'Serialized widget form data to encode into instance settings.' ), 87 'type' => 'string', 88 'sanitize_callback' => static function ( $form_data ) { 89 $array = array(); 90 wp_parse_str( $form_data, $array ); 91 return $array; 92 }, 93 ), 94 ), 95 array( 96 'methods' => WP_REST_Server::CREATABLE, 97 'permission_callback' => array( $this, 'get_item_permissions_check' ), 98 'callback' => array( $this, 'encode_form_data' ), 99 ), 100 ) 101 ); 102 103 register_rest_route( 104 $this->namespace, 105 '/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)/render', 106 array( 107 array( 108 'methods' => WP_REST_Server::CREATABLE, 109 'permission_callback' => array( $this, 'get_item_permissions_check' ), 110 'callback' => array( $this, 'render' ), 111 'args' => array( 112 'id' => array( 113 'description' => __( 'The widget type id.' ), 114 'type' => 'string', 115 'required' => true, 116 ), 117 'instance' => array( 118 'description' => __( 'Current instance settings of the widget.' ), 119 'type' => 'object', 120 ), 121 ), 122 ), 123 ) 124 ); 125 } 126 127 /** 128 * Checks whether a given request has permission to read widget types. 129 * 130 * @since 5.8.0 131 * 132 * @param WP_REST_Request $request Full details about the request. 133 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 134 */ 135 public function get_items_permissions_check( $request ) { 136 return $this->check_read_permission(); 137 } 138 139 /** 140 * Retrieves the list of all widget types. 141 * 142 * @since 5.8.0 143 * 144 * @param WP_REST_Request $request Full details about the request. 145 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 146 */ 147 public function get_items( $request ) { 148 $data = array(); 149 foreach ( $this->get_widgets() as $widget ) { 150 $widget_type = $this->prepare_item_for_response( $widget, $request ); 151 $data[] = $this->prepare_response_for_collection( $widget_type ); 152 } 153 154 return rest_ensure_response( $data ); 155 } 156 157 /** 158 * Checks if a given request has access to read a widget type. 159 * 160 * @since 5.8.0 161 * 162 * @param WP_REST_Request $request Full details about the request. 163 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. 164 */ 165 public function get_item_permissions_check( $request ) { 166 $check = $this->check_read_permission(); 167 if ( is_wp_error( $check ) ) { 168 return $check; 169 } 170 $widget_id = $request['id']; 171 $widget_type = $this->get_widget( $widget_id ); 172 if ( is_wp_error( $widget_type ) ) { 173 return $widget_type; 174 } 175 176 return true; 177 } 178 179 /** 180 * Checks whether the user can read widget types. 181 * 182 * @since 5.8.0 183 * 184 * @return true|WP_Error True if the widget type is visible, WP_Error otherwise. 185 */ 186 protected function check_read_permission() { 187 if ( ! current_user_can( 'edit_theme_options' ) ) { 188 return new WP_Error( 189 'rest_cannot_manage_widgets', 190 __( 'Sorry, you are not allowed to manage widgets on this site.' ), 191 array( 192 'status' => rest_authorization_required_code(), 193 ) 194 ); 195 } 196 197 return true; 198 } 199 200 /** 201 * Gets the details about the requested widget. 202 * 203 * @since 5.8.0 204 * 205 * @param string $id The widget type id. 206 * @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise. 207 */ 208 public function get_widget( $id ) { 209 foreach ( $this->get_widgets() as $widget ) { 210 if ( $id === $widget['id'] ) { 211 return $widget; 212 } 213 } 214 215 return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) ); 216 } 217 218 /** 219 * Normalize array of widgets. 220 * 221 * @since 5.8.0 222 * 223 * @global WP_Widget_Factory $wp_widget_factory 224 * @global array $wp_registered_widgets The list of registered widgets. 225 * 226 * @return array Array of widgets. 227 */ 228 protected function get_widgets() { 229 global $wp_widget_factory, $wp_registered_widgets; 230 231 $widgets = array(); 232 233 foreach ( $wp_registered_widgets as $widget ) { 234 $parsed_id = wp_parse_widget_id( $widget['id'] ); 235 $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] ); 236 237 $widget['id'] = $parsed_id['id_base']; 238 $widget['is_multi'] = (bool) $widget_object; 239 240 if ( isset( $widget['name'] ) ) { 241 $widget['name'] = html_entity_decode( $widget['name'], ENT_QUOTES, get_bloginfo( 'charset' ) ); 242 } 243 244 if ( isset( $widget['description'] ) ) { 245 $widget['description'] = html_entity_decode( $widget['description'], ENT_QUOTES, get_bloginfo( 'charset' ) ); 246 } 247 248 unset( $widget['callback'] ); 249 250 $classname = ''; 251 foreach ( (array) $widget['classname'] as $cn ) { 252 if ( is_string( $cn ) ) { 253 $classname .= '_' . $cn; 254 } elseif ( is_object( $cn ) ) { 255 $classname .= '_' . get_class( $cn ); 256 } 257 } 258 $widget['classname'] = ltrim( $classname, '_' ); 259 260 $widgets[ $widget['id'] ] = $widget; 261 } 262 263 ksort( $widgets ); 264 265 return $widgets; 266 } 267 268 /** 269 * Retrieves a single widget type from the collection. 270 * 271 * @since 5.8.0 272 * 273 * @param WP_REST_Request $request Full details about the request. 274 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 275 */ 276 public function get_item( $request ) { 277 $widget_id = $request['id']; 278 $widget_type = $this->get_widget( $widget_id ); 279 if ( is_wp_error( $widget_type ) ) { 280 return $widget_type; 281 } 282 $data = $this->prepare_item_for_response( $widget_type, $request ); 283 284 return rest_ensure_response( $data ); 285 } 286 287 /** 288 * Prepares a widget type object for serialization. 289 * 290 * @since 5.8.0 291 * @since 5.9.0 Renamed `$widget_type` to `$item` to match parent class for PHP 8 named parameter support. 292 * 293 * @param array $item Widget type data. 294 * @param WP_REST_Request $request Full details about the request. 295 * @return WP_REST_Response Widget type data. 296 */ 297 public function prepare_item_for_response( $item, $request ) { 298 // Restores the more descriptive, specific name for use within this method. 299 $widget_type = $item; 300 301 $fields = $this->get_fields_for_response( $request ); 302 $data = array( 303 'id' => $widget_type['id'], 304 ); 305 306 $schema = $this->get_item_schema(); 307 $extra_fields = array( 308 'name', 309 'description', 310 'is_multi', 311 'classname', 312 'widget_class', 313 'option_name', 314 'customize_selective_refresh', 315 ); 316 317 foreach ( $extra_fields as $extra_field ) { 318 if ( ! rest_is_field_included( $extra_field, $fields ) ) { 319 continue; 320 } 321 322 if ( isset( $widget_type[ $extra_field ] ) ) { 323 $field = $widget_type[ $extra_field ]; 324 } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { 325 $field = $schema['properties'][ $extra_field ]['default']; 326 } else { 327 $field = ''; 328 } 329 330 $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); 331 } 332 333 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 334 $data = $this->add_additional_fields_to_object( $data, $request ); 335 $data = $this->filter_response_by_context( $data, $context ); 336 337 $response = rest_ensure_response( $data ); 338 339 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 340 $response->add_links( $this->prepare_links( $widget_type ) ); 341 } 342 343 /** 344 * Filters the REST API response for a widget type. 345 * 346 * @since 5.8.0 347 * 348 * @param WP_REST_Response $response The response object. 349 * @param array $widget_type The array of widget data. 350 * @param WP_REST_Request $request The request object. 351 */ 352 return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request ); 353 } 354 355 /** 356 * Prepares links for the widget type. 357 * 358 * @since 5.8.0 359 * 360 * @param array $widget_type Widget type data. 361 * @return array Links for the given widget type. 362 */ 363 protected function prepare_links( $widget_type ) { 364 return array( 365 'collection' => array( 366 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 367 ), 368 'self' => array( 369 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ), 370 ), 371 ); 372 } 373 374 /** 375 * Retrieves the widget type's schema, conforming to JSON Schema. 376 * 377 * @since 5.8.0 378 * 379 * @return array Item schema data. 380 */ 381 public function get_item_schema() { 382 if ( $this->schema ) { 383 return $this->add_additional_fields_schema( $this->schema ); 384 } 385 386 $schema = array( 387 '$schema' => 'http://json-schema.org/draft-04/schema#', 388 'title' => 'widget-type', 389 'type' => 'object', 390 'properties' => array( 391 'id' => array( 392 'description' => __( 'Unique slug identifying the widget type.' ), 393 'type' => 'string', 394 'context' => array( 'embed', 'view', 'edit' ), 395 'readonly' => true, 396 ), 397 'name' => array( 398 'description' => __( 'Human-readable name identifying the widget type.' ), 399 'type' => 'string', 400 'default' => '', 401 'context' => array( 'embed', 'view', 'edit' ), 402 'readonly' => true, 403 ), 404 'description' => array( 405 'description' => __( 'Description of the widget.' ), 406 'type' => 'string', 407 'default' => '', 408 'context' => array( 'view', 'edit', 'embed' ), 409 ), 410 'is_multi' => array( 411 'description' => __( 'Whether the widget supports multiple instances' ), 412 'type' => 'boolean', 413 'context' => array( 'view', 'edit', 'embed' ), 414 'readonly' => true, 415 ), 416 'classname' => array( 417 'description' => __( 'Class name' ), 418 'type' => 'string', 419 'default' => '', 420 'context' => array( 'embed', 'view', 'edit' ), 421 'readonly' => true, 422 ), 423 ), 424 ); 425 426 $this->schema = $schema; 427 428 return $this->add_additional_fields_schema( $this->schema ); 429 } 430 431 /** 432 * An RPC-style endpoint which can be used by clients to turn user input in 433 * a widget admin form into an encoded instance object. 434 * 435 * Accepts: 436 * 437 * - id: A widget type ID. 438 * - instance: A widget's encoded instance object. Optional. 439 * - form_data: Form data from submitting a widget's admin form. Optional. 440 * 441 * Returns: 442 * - instance: The encoded instance object after updating the widget with 443 * the given form data. 444 * - form: The widget's admin form after updating the widget with the 445 * given form data. 446 * 447 * @since 5.8.0 448 * 449 * @global WP_Widget_Factory $wp_widget_factory 450 * 451 * @param WP_REST_Request $request Full details about the request. 452 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 453 */ 454 public function encode_form_data( $request ) { 455 global $wp_widget_factory; 456 457 $id = $request['id']; 458 $widget_object = $wp_widget_factory->get_widget_object( $id ); 459 460 if ( ! $widget_object ) { 461 return new WP_Error( 462 'rest_invalid_widget', 463 __( 'Cannot preview a widget that does not extend WP_Widget.' ), 464 array( 'status' => 400 ) 465 ); 466 } 467 468 /* 469 * Set the widget's number so that the id attributes in the HTML that we 470 * return are predictable. 471 */ 472 if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) { 473 $widget_object->_set( (int) $request['number'] ); 474 } else { 475 $widget_object->_set( -1 ); 476 } 477 478 if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) { 479 $serialized_instance = base64_decode( $request['instance']['encoded'] ); 480 if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) { 481 return new WP_Error( 482 'rest_invalid_widget', 483 __( 'The provided instance is malformed.' ), 484 array( 'status' => 400 ) 485 ); 486 } 487 $instance = unserialize( $serialized_instance ); 488 } else { 489 $instance = array(); 490 } 491 492 if ( 493 isset( $request['form_data'][ "widget-$id" ] ) && 494 is_array( $request['form_data'][ "widget-$id" ] ) 495 ) { 496 $new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0]; 497 $old_instance = $instance; 498 499 $instance = $widget_object->update( $new_instance, $old_instance ); 500 501 /** This filter is documented in wp-includes/class-wp-widget.php */ 502 $instance = apply_filters( 503 'widget_update_callback', 504 $instance, 505 $new_instance, 506 $old_instance, 507 $widget_object 508 ); 509 } 510 511 $serialized_instance = serialize( $instance ); 512 $widget_key = $wp_widget_factory->get_widget_key( $id ); 513 514 $response = array( 515 'form' => trim( 516 $this->get_widget_form( 517 $widget_object, 518 $instance 519 ) 520 ), 521 'preview' => trim( 522 $this->get_widget_preview( 523 $widget_key, 524 $instance 525 ) 526 ), 527 'instance' => array( 528 'encoded' => base64_encode( $serialized_instance ), 529 'hash' => wp_hash( $serialized_instance ), 530 ), 531 ); 532 533 if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { 534 // Use new stdClass so that JSON result is {} and not []. 535 $response['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance; 536 } 537 538 return rest_ensure_response( $response ); 539 } 540 541 /** 542 * Returns the output of WP_Widget::widget() when called with the provided 543 * instance. Used by encode_form_data() to preview a widget. 544 545 * @since 5.8.0 546 * 547 * @param string $widget The widget's PHP class name (see class-wp-widget.php). 548 * @param array $instance Widget instance settings. 549 * @return string 550 */ 551 private function get_widget_preview( $widget, $instance ) { 552 ob_start(); 553 the_widget( $widget, $instance ); 554 return ob_get_clean(); 555 } 556 557 /** 558 * Returns the output of WP_Widget::form() when called with the provided 559 * instance. Used by encode_form_data() to preview a widget's form. 560 * 561 * @since 5.8.0 562 * 563 * @param WP_Widget $widget_object Widget object to call widget() on. 564 * @param array $instance Widget instance settings. 565 * @return string 566 */ 567 private function get_widget_form( $widget_object, $instance ) { 568 ob_start(); 569 570 /** This filter is documented in wp-includes/class-wp-widget.php */ 571 $instance = apply_filters( 572 'widget_form_callback', 573 $instance, 574 $widget_object 575 ); 576 577 if ( false !== $instance ) { 578 $return = $widget_object->form( $instance ); 579 580 /** This filter is documented in wp-includes/class-wp-widget.php */ 581 do_action_ref_array( 582 'in_widget_form', 583 array( &$widget_object, &$return, $instance ) 584 ); 585 } 586 587 return ob_get_clean(); 588 } 589 590 /** 591 * Renders a single Legacy Widget and wraps it in a JSON-encodable array. 592 * 593 * @since 5.9.0 594 * 595 * @param WP_REST_Request $request Full details about the request. 596 * 597 * @return array An array with rendered Legacy Widget HTML. 598 */ 599 public function render( $request ) { 600 return array( 601 'preview' => $this->render_legacy_widget_preview_iframe( 602 $request['id'], 603 isset( $request['instance'] ) ? $request['instance'] : null 604 ), 605 ); 606 } 607 608 /** 609 * Renders a page containing a preview of the requested Legacy Widget block. 610 * 611 * @since 5.9.0 612 * 613 * @param string $id_base The id base of the requested widget. 614 * @param array $instance The widget instance attributes. 615 * 616 * @return string Rendered Legacy Widget block preview. 617 */ 618 private function render_legacy_widget_preview_iframe( $id_base, $instance ) { 619 if ( ! defined( 'IFRAME_REQUEST' ) ) { 620 define( 'IFRAME_REQUEST', true ); 621 } 622 623 ob_start(); 624 ?> 625 <!doctype html> 626 <html <?php language_attributes(); ?>> 627 <head> 628 <meta charset="<?php bloginfo( 'charset' ); ?>" /> 629 <meta name="viewport" content="width=device-width, initial-scale=1" /> 630 <link rel="profile" href="https://gmpg.org/xfn/11" /> 631 <?php wp_head(); ?> 632 <style> 633 /* Reset theme styles */ 634 html, body, #page, #content { 635 padding: 0 !important; 636 margin: 0 !important; 637 } 638 </style> 639 </head> 640 <body <?php body_class(); ?>> 641 <div id="page" class="site"> 642 <div id="content" class="site-content"> 643 <?php 644 $registry = WP_Block_Type_Registry::get_instance(); 645 $block = $registry->get_registered( 'core/legacy-widget' ); 646 echo $block->render( 647 array( 648 'idBase' => $id_base, 649 'instance' => $instance, 650 ) 651 ); 652 ?> 653 </div><!-- #content --> 654 </div><!-- #page --> 655 <?php wp_footer(); ?> 656 </body> 657 </html> 658 <?php 659 return ob_get_clean(); 660 } 661 662 /** 663 * Retrieves the query params for collections. 664 * 665 * @since 5.8.0 666 * 667 * @return array Collection parameters. 668 */ 669 public function get_collection_params() { 670 return array( 671 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 672 ); 673 } 674 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |