[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-widget-types-controller.php (source)

   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  }


Generated: Fri Dec 6 01:00:02 2024 Cross-referenced by PHPXref 0.7.1