[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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 ( $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  }


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref