[ Index ] |
PHP Cross Reference of WordPress |
[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( $string ) { 89 $array = array(); 90 wp_parse_str( $string, $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 $fields = $this->get_fields_for_response( $request ); 301 $data = array( 302 'id' => $widget_type['id'], 303 ); 304 305 $schema = $this->get_item_schema(); 306 $extra_fields = array( 307 'name', 308 'description', 309 'is_multi', 310 'classname', 311 'widget_class', 312 'option_name', 313 'customize_selective_refresh', 314 ); 315 316 foreach ( $extra_fields as $extra_field ) { 317 if ( ! rest_is_field_included( $extra_field, $fields ) ) { 318 continue; 319 } 320 321 if ( isset( $widget_type[ $extra_field ] ) ) { 322 $field = $widget_type[ $extra_field ]; 323 } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { 324 $field = $schema['properties'][ $extra_field ]['default']; 325 } else { 326 $field = ''; 327 } 328 329 $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); 330 } 331 332 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 333 $data = $this->add_additional_fields_to_object( $data, $request ); 334 $data = $this->filter_response_by_context( $data, $context ); 335 336 $response = rest_ensure_response( $data ); 337 338 $response->add_links( $this->prepare_links( $widget_type ) ); 339 340 /** 341 * Filters the REST API response for a widget type. 342 * 343 * @since 5.8.0 344 * 345 * @param WP_REST_Response $response The response object. 346 * @param array $widget_type The array of widget data. 347 * @param WP_REST_Request $request The request object. 348 */ 349 return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request ); 350 } 351 352 /** 353 * Prepares links for the widget type. 354 * 355 * @since 5.8.0 356 * 357 * @param array $widget_type Widget type data. 358 * @return array Links for the given widget type. 359 */ 360 protected function prepare_links( $widget_type ) { 361 return array( 362 'collection' => array( 363 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 364 ), 365 'self' => array( 366 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ), 367 ), 368 ); 369 } 370 371 /** 372 * Retrieves the widget type's schema, conforming to JSON Schema. 373 * 374 * @since 5.8.0 375 * 376 * @return array Item schema data. 377 */ 378 public function get_item_schema() { 379 if ( $this->schema ) { 380 return $this->add_additional_fields_schema( $this->schema ); 381 } 382 383 $schema = array( 384 '$schema' => 'http://json-schema.org/draft-04/schema#', 385 'title' => 'widget-type', 386 'type' => 'object', 387 'properties' => array( 388 'id' => array( 389 'description' => __( 'Unique slug identifying the widget type.' ), 390 'type' => 'string', 391 'context' => array( 'embed', 'view', 'edit' ), 392 'readonly' => true, 393 ), 394 'name' => array( 395 'description' => __( 'Human-readable name identifying the widget type.' ), 396 'type' => 'string', 397 'default' => '', 398 'context' => array( 'embed', 'view', 'edit' ), 399 'readonly' => true, 400 ), 401 'description' => array( 402 'description' => __( 'Description of the widget.' ), 403 'type' => 'string', 404 'default' => '', 405 'context' => array( 'view', 'edit', 'embed' ), 406 ), 407 'is_multi' => array( 408 'description' => __( 'Whether the widget supports multiple instances' ), 409 'type' => 'boolean', 410 'context' => array( 'view', 'edit', 'embed' ), 411 'readonly' => true, 412 ), 413 'classname' => array( 414 'description' => __( 'Class name' ), 415 'type' => 'string', 416 'default' => '', 417 'context' => array( 'embed', 'view', 'edit' ), 418 'readonly' => true, 419 ), 420 ), 421 ); 422 423 $this->schema = $schema; 424 425 return $this->add_additional_fields_schema( $this->schema ); 426 } 427 428 /** 429 * An RPC-style endpoint which can be used by clients to turn user input in 430 * a widget admin form into an encoded instance object. 431 * 432 * Accepts: 433 * 434 * - id: A widget type ID. 435 * - instance: A widget's encoded instance object. Optional. 436 * - form_data: Form data from submitting a widget's admin form. Optional. 437 * 438 * Returns: 439 * - instance: The encoded instance object after updating the widget with 440 * the given form data. 441 * - form: The widget's admin form after updating the widget with the 442 * given form data. 443 * 444 * @since 5.8.0 445 * 446 * @global WP_Widget_Factory $wp_widget_factory 447 * 448 * @param WP_REST_Request $request Full details about the request. 449 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 450 */ 451 public function encode_form_data( $request ) { 452 global $wp_widget_factory; 453 454 $id = $request['id']; 455 $widget_object = $wp_widget_factory->get_widget_object( $id ); 456 457 if ( ! $widget_object ) { 458 return new WP_Error( 459 'rest_invalid_widget', 460 __( 'Cannot preview a widget that does not extend WP_Widget.' ), 461 array( 'status' => 400 ) 462 ); 463 } 464 465 // Set the widget's number so that the id attributes in the HTML that we 466 // return are predictable. 467 if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) { 468 $widget_object->_set( (int) $request['number'] ); 469 } else { 470 $widget_object->_set( -1 ); 471 } 472 473 if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) { 474 $serialized_instance = base64_decode( $request['instance']['encoded'] ); 475 if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) { 476 return new WP_Error( 477 'rest_invalid_widget', 478 __( 'The provided instance is malformed.' ), 479 array( 'status' => 400 ) 480 ); 481 } 482 $instance = unserialize( $serialized_instance ); 483 } else { 484 $instance = array(); 485 } 486 487 if ( 488 isset( $request['form_data'][ "widget-$id" ] ) && 489 is_array( $request['form_data'][ "widget-$id" ] ) 490 ) { 491 $new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0]; 492 $old_instance = $instance; 493 494 $instance = $widget_object->update( $new_instance, $old_instance ); 495 496 /** This filter is documented in wp-includes/class-wp-widget.php */ 497 $instance = apply_filters( 498 'widget_update_callback', 499 $instance, 500 $new_instance, 501 $old_instance, 502 $widget_object 503 ); 504 } 505 506 $serialized_instance = serialize( $instance ); 507 $widget_key = $wp_widget_factory->get_widget_key( $id ); 508 509 $response = array( 510 'form' => trim( 511 $this->get_widget_form( 512 $widget_object, 513 $instance 514 ) 515 ), 516 'preview' => trim( 517 $this->get_widget_preview( 518 $widget_key, 519 $instance 520 ) 521 ), 522 'instance' => array( 523 'encoded' => base64_encode( $serialized_instance ), 524 'hash' => wp_hash( $serialized_instance ), 525 ), 526 ); 527 528 if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) { 529 // Use new stdClass so that JSON result is {} and not []. 530 $response['instance']['raw'] = empty( $instance ) ? new stdClass : $instance; 531 } 532 533 return rest_ensure_response( $response ); 534 } 535 536 /** 537 * Returns the output of WP_Widget::widget() when called with the provided 538 * instance. Used by encode_form_data() to preview a widget. 539 540 * @since 5.8.0 541 * 542 * @param string $widget The widget's PHP class name (see class-wp-widget.php). 543 * @param array $instance Widget instance settings. 544 * @return string 545 */ 546 private function get_widget_preview( $widget, $instance ) { 547 ob_start(); 548 the_widget( $widget, $instance ); 549 return ob_get_clean(); 550 } 551 552 /** 553 * Returns the output of WP_Widget::form() when called with the provided 554 * instance. Used by encode_form_data() to preview a widget's form. 555 * 556 * @since 5.8.0 557 * 558 * @param WP_Widget $widget_object Widget object to call widget() on. 559 * @param array $instance Widget instance settings. 560 * @return string 561 */ 562 private function get_widget_form( $widget_object, $instance ) { 563 ob_start(); 564 565 /** This filter is documented in wp-includes/class-wp-widget.php */ 566 $instance = apply_filters( 567 'widget_form_callback', 568 $instance, 569 $widget_object 570 ); 571 572 if ( false !== $instance ) { 573 $return = $widget_object->form( $instance ); 574 575 /** This filter is documented in wp-includes/class-wp-widget.php */ 576 do_action_ref_array( 577 'in_widget_form', 578 array( &$widget_object, &$return, $instance ) 579 ); 580 } 581 582 return ob_get_clean(); 583 } 584 585 /** 586 * Renders a single Legacy Widget and wraps it in a JSON-encodable array. 587 * 588 * @since 5.9.0 589 * 590 * @param WP_REST_Request $request Full details about the request. 591 * 592 * @return array An array with rendered Legacy Widget HTML. 593 */ 594 public function render( $request ) { 595 return array( 596 'preview' => $this->render_legacy_widget_preview_iframe( 597 $request['id'], 598 isset( $request['instance'] ) ? $request['instance'] : null 599 ), 600 ); 601 } 602 603 /** 604 * Renders a page containing a preview of the requested Legacy Widget block. 605 * 606 * @since 5.9.0 607 * 608 * @param string $id_base The id base of the requested widget. 609 * @param array $instance The widget instance attributes. 610 * 611 * @return string Rendered Legacy Widget block preview. 612 */ 613 private function render_legacy_widget_preview_iframe( $id_base, $instance ) { 614 if ( ! defined( 'IFRAME_REQUEST' ) ) { 615 define( 'IFRAME_REQUEST', true ); 616 } 617 618 ob_start(); 619 ?> 620 <!doctype html> 621 <html <?php language_attributes(); ?>> 622 <head> 623 <meta charset="<?php bloginfo( 'charset' ); ?>" /> 624 <meta name="viewport" content="width=device-width, initial-scale=1" /> 625 <link rel="profile" href="https://gmpg.org/xfn/11" /> 626 <?php wp_head(); ?> 627 <style> 628 /* Reset theme styles */ 629 html, body, #page, #content { 630 padding: 0 !important; 631 margin: 0 !important; 632 } 633 </style> 634 </head> 635 <body <?php body_class(); ?>> 636 <div id="page" class="site"> 637 <div id="content" class="site-content"> 638 <?php 639 $registry = WP_Block_Type_Registry::get_instance(); 640 $block = $registry->get_registered( 'core/legacy-widget' ); 641 echo $block->render( 642 array( 643 'idBase' => $id_base, 644 'instance' => $instance, 645 ) 646 ); 647 ?> 648 </div><!-- #content --> 649 </div><!-- #page --> 650 <?php wp_footer(); ?> 651 </body> 652 </html> 653 <?php 654 return ob_get_clean(); 655 } 656 657 /** 658 * Retrieves the query params for collections. 659 * 660 * @since 5.8.0 661 * 662 * @return array Collection parameters. 663 */ 664 public function get_collection_params() { 665 return array( 666 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 667 ); 668 } 669 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Dec 6 01:00:02 2024 | Cross-referenced by PHPXref 0.7.1 |