[ 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  
 104      /**
 105       * Checks whether a given request has permission to read widget types.
 106       *
 107       * @since 5.8.0
 108       *
 109       * @param WP_REST_Request $request Full details about the request.
 110       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
 111       */
 112  	public function get_items_permissions_check( $request ) {
 113          return $this->check_read_permission();
 114      }
 115  
 116      /**
 117       * Retrieves the list of all widget types.
 118       *
 119       * @since 5.8.0
 120       *
 121       * @param WP_REST_Request $request Full details about the request.
 122       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 123       */
 124  	public function get_items( $request ) {
 125          $data = array();
 126          foreach ( $this->get_widgets() as $widget ) {
 127              $widget_type = $this->prepare_item_for_response( $widget, $request );
 128              $data[]      = $this->prepare_response_for_collection( $widget_type );
 129          }
 130  
 131          return rest_ensure_response( $data );
 132      }
 133  
 134      /**
 135       * Checks if a given request has access to read a widget type.
 136       *
 137       * @since 5.8.0
 138       *
 139       * @param WP_REST_Request $request Full details about the request.
 140       * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
 141       */
 142  	public function get_item_permissions_check( $request ) {
 143          $check = $this->check_read_permission();
 144          if ( is_wp_error( $check ) ) {
 145              return $check;
 146          }
 147          $widget_id   = $request['id'];
 148          $widget_type = $this->get_widget( $widget_id );
 149          if ( is_wp_error( $widget_type ) ) {
 150              return $widget_type;
 151          }
 152  
 153          return true;
 154      }
 155  
 156      /**
 157       * Checks whether the user can read widget types.
 158       *
 159       * @since 5.8.0
 160       *
 161       * @return true|WP_Error True if the widget type is visible, WP_Error otherwise.
 162       */
 163  	protected function check_read_permission() {
 164          if ( ! current_user_can( 'edit_theme_options' ) ) {
 165              return new WP_Error(
 166                  'rest_cannot_manage_widgets',
 167                  __( 'Sorry, you are not allowed to manage widgets on this site.' ),
 168                  array(
 169                      'status' => rest_authorization_required_code(),
 170                  )
 171              );
 172          }
 173  
 174          return true;
 175      }
 176  
 177      /**
 178       * Gets the details about the requested widget.
 179       *
 180       * @since 5.8.0
 181       *
 182       * @param string $id The widget type id.
 183       * @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise.
 184       */
 185  	public function get_widget( $id ) {
 186          foreach ( $this->get_widgets() as $widget ) {
 187              if ( $id === $widget['id'] ) {
 188                  return $widget;
 189              }
 190          }
 191  
 192          return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) );
 193      }
 194  
 195      /**
 196       * Normalize array of widgets.
 197       *
 198       * @since 5.8.0
 199       *
 200       * @global WP_Widget_Factory $wp_widget_factory
 201       * @global array             $wp_registered_widgets The list of registered widgets.
 202       *
 203       * @return array Array of widgets.
 204       */
 205  	protected function get_widgets() {
 206          global $wp_widget_factory, $wp_registered_widgets;
 207  
 208          $widgets = array();
 209  
 210          foreach ( $wp_registered_widgets as $widget ) {
 211              $parsed_id     = wp_parse_widget_id( $widget['id'] );
 212              $widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
 213  
 214              $widget['id']       = $parsed_id['id_base'];
 215              $widget['is_multi'] = (bool) $widget_object;
 216  
 217              if ( isset( $widget['name'] ) ) {
 218                  $widget['name'] = html_entity_decode( $widget['name'], ENT_QUOTES, get_bloginfo( 'charset' ) );
 219              }
 220  
 221              if ( isset( $widget['description'] ) ) {
 222                  $widget['description'] = html_entity_decode( $widget['description'], ENT_QUOTES, get_bloginfo( 'charset' ) );
 223              }
 224  
 225              unset( $widget['callback'] );
 226  
 227              $classname = '';
 228              foreach ( (array) $widget['classname'] as $cn ) {
 229                  if ( is_string( $cn ) ) {
 230                      $classname .= '_' . $cn;
 231                  } elseif ( is_object( $cn ) ) {
 232                      $classname .= '_' . get_class( $cn );
 233                  }
 234              }
 235              $widget['classname'] = ltrim( $classname, '_' );
 236  
 237              $widgets[ $widget['id'] ] = $widget;
 238          }
 239  
 240          return $widgets;
 241      }
 242  
 243      /**
 244       * Retrieves a single widget type from the collection.
 245       *
 246       * @since 5.8.0
 247       *
 248       * @param WP_REST_Request $request Full details about the request.
 249       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 250       */
 251  	public function get_item( $request ) {
 252          $widget_id   = $request['id'];
 253          $widget_type = $this->get_widget( $widget_id );
 254          if ( is_wp_error( $widget_type ) ) {
 255              return $widget_type;
 256          }
 257          $data = $this->prepare_item_for_response( $widget_type, $request );
 258  
 259          return rest_ensure_response( $data );
 260      }
 261  
 262      /**
 263       * Prepares a widget type object for serialization.
 264       *
 265       * @since 5.8.0
 266       * @since 5.9.0 Renamed `$widget_type` to `$item` to match parent class for PHP 8 named parameter support.
 267       *
 268       * @param array           $item    Widget type data.
 269       * @param WP_REST_Request $request Full details about the request.
 270       * @return WP_REST_Response Widget type data.
 271       */
 272  	public function prepare_item_for_response( $item, $request ) {
 273          // Restores the more descriptive, specific name for use within this method.
 274          $widget_type = $item;
 275          $fields      = $this->get_fields_for_response( $request );
 276          $data        = array(
 277              'id' => $widget_type['id'],
 278          );
 279  
 280          $schema       = $this->get_item_schema();
 281          $extra_fields = array(
 282              'name',
 283              'description',
 284              'is_multi',
 285              'classname',
 286              'widget_class',
 287              'option_name',
 288              'customize_selective_refresh',
 289          );
 290  
 291          foreach ( $extra_fields as $extra_field ) {
 292              if ( ! rest_is_field_included( $extra_field, $fields ) ) {
 293                  continue;
 294              }
 295  
 296              if ( isset( $widget_type[ $extra_field ] ) ) {
 297                  $field = $widget_type[ $extra_field ];
 298              } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) {
 299                  $field = $schema['properties'][ $extra_field ]['default'];
 300              } else {
 301                  $field = '';
 302              }
 303  
 304              $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] );
 305          }
 306  
 307          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 308          $data    = $this->add_additional_fields_to_object( $data, $request );
 309          $data    = $this->filter_response_by_context( $data, $context );
 310  
 311          $response = rest_ensure_response( $data );
 312  
 313          $response->add_links( $this->prepare_links( $widget_type ) );
 314  
 315          /**
 316           * Filters the REST API response for a widget type.
 317           *
 318           * @since 5.8.0
 319           *
 320           * @param WP_REST_Response $response    The response object.
 321           * @param array            $widget_type The array of widget data.
 322           * @param WP_REST_Request  $request     The request object.
 323           */
 324          return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request );
 325      }
 326  
 327      /**
 328       * Prepares links for the widget type.
 329       *
 330       * @since 5.8.0
 331       *
 332       * @param array $widget_type Widget type data.
 333       * @return array Links for the given widget type.
 334       */
 335  	protected function prepare_links( $widget_type ) {
 336          return array(
 337              'collection' => array(
 338                  'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
 339              ),
 340              'self'       => array(
 341                  'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ),
 342              ),
 343          );
 344      }
 345  
 346      /**
 347       * Retrieves the widget type's schema, conforming to JSON Schema.
 348       *
 349       * @since 5.8.0
 350       *
 351       * @return array Item schema data.
 352       */
 353  	public function get_item_schema() {
 354          if ( $this->schema ) {
 355              return $this->add_additional_fields_schema( $this->schema );
 356          }
 357  
 358          $schema = array(
 359              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 360              'title'      => 'widget-type',
 361              'type'       => 'object',
 362              'properties' => array(
 363                  'id'          => array(
 364                      'description' => __( 'Unique slug identifying the widget type.' ),
 365                      'type'        => 'string',
 366                      'context'     => array( 'embed', 'view', 'edit' ),
 367                      'readonly'    => true,
 368                  ),
 369                  'name'        => array(
 370                      'description' => __( 'Human-readable name identifying the widget type.' ),
 371                      'type'        => 'string',
 372                      'default'     => '',
 373                      'context'     => array( 'embed', 'view', 'edit' ),
 374                      'readonly'    => true,
 375                  ),
 376                  'description' => array(
 377                      'description' => __( 'Description of the widget.' ),
 378                      'type'        => 'string',
 379                      'default'     => '',
 380                      'context'     => array( 'view', 'edit', 'embed' ),
 381                  ),
 382                  'is_multi'    => array(
 383                      'description' => __( 'Whether the widget supports multiple instances' ),
 384                      'type'        => 'boolean',
 385                      'context'     => array( 'view', 'edit', 'embed' ),
 386                      'readonly'    => true,
 387                  ),
 388                  'classname'   => array(
 389                      'description' => __( 'Class name' ),
 390                      'type'        => 'string',
 391                      'default'     => '',
 392                      'context'     => array( 'embed', 'view', 'edit' ),
 393                      'readonly'    => true,
 394                  ),
 395              ),
 396          );
 397  
 398          $this->schema = $schema;
 399  
 400          return $this->add_additional_fields_schema( $this->schema );
 401      }
 402  
 403      /**
 404       * An RPC-style endpoint which can be used by clients to turn user input in
 405       * a widget admin form into an encoded instance object.
 406       *
 407       * Accepts:
 408       *
 409       * - id:        A widget type ID.
 410       * - instance:  A widget's encoded instance object. Optional.
 411       * - form_data: Form data from submitting a widget's admin form. Optional.
 412       *
 413       * Returns:
 414       * - instance: The encoded instance object after updating the widget with
 415       *             the given form data.
 416       * - form:     The widget's admin form after updating the widget with the
 417       *             given form data.
 418       *
 419       * @since 5.8.0
 420       *
 421       * @global WP_Widget_Factory $wp_widget_factory
 422       *
 423       * @param WP_REST_Request $request Full details about the request.
 424       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 425       */
 426  	public function encode_form_data( $request ) {
 427          global $wp_widget_factory;
 428  
 429          $id            = $request['id'];
 430          $widget_object = $wp_widget_factory->get_widget_object( $id );
 431  
 432          if ( ! $widget_object ) {
 433              return new WP_Error(
 434                  'rest_invalid_widget',
 435                  __( 'Cannot preview a widget that does not extend WP_Widget.' ),
 436                  array( 'status' => 400 )
 437              );
 438          }
 439  
 440          // Set the widget's number so that the id attributes in the HTML that we
 441          // return are predictable.
 442          if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) {
 443              $widget_object->_set( (int) $request['number'] );
 444          } else {
 445              $widget_object->_set( -1 );
 446          }
 447  
 448          if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
 449              $serialized_instance = base64_decode( $request['instance']['encoded'] );
 450              if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
 451                  return new WP_Error(
 452                      'rest_invalid_widget',
 453                      __( 'The provided instance is malformed.' ),
 454                      array( 'status' => 400 )
 455                  );
 456              }
 457              $instance = unserialize( $serialized_instance );
 458          } else {
 459              $instance = array();
 460          }
 461  
 462          if (
 463              isset( $request['form_data'][ "widget-$id" ] ) &&
 464              is_array( $request['form_data'][ "widget-$id" ] )
 465          ) {
 466              $new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0];
 467              $old_instance = $instance;
 468  
 469              $instance = $widget_object->update( $new_instance, $old_instance );
 470  
 471              /** This filter is documented in wp-includes/class-wp-widget.php */
 472              $instance = apply_filters(
 473                  'widget_update_callback',
 474                  $instance,
 475                  $new_instance,
 476                  $old_instance,
 477                  $widget_object
 478              );
 479          }
 480  
 481          $serialized_instance = serialize( $instance );
 482          $widget_key          = $wp_widget_factory->get_widget_key( $id );
 483  
 484          $response = array(
 485              'form'     => trim(
 486                  $this->get_widget_form(
 487                      $widget_object,
 488                      $instance
 489                  )
 490              ),
 491              'preview'  => trim(
 492                  $this->get_widget_preview(
 493                      $widget_key,
 494                      $instance
 495                  )
 496              ),
 497              'instance' => array(
 498                  'encoded' => base64_encode( $serialized_instance ),
 499                  'hash'    => wp_hash( $serialized_instance ),
 500              ),
 501          );
 502  
 503          if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
 504              // Use new stdClass so that JSON result is {} and not [].
 505              $response['instance']['raw'] = empty( $instance ) ? new stdClass : $instance;
 506          }
 507  
 508          return rest_ensure_response( $response );
 509      }
 510  
 511      /**
 512       * Returns the output of WP_Widget::widget() when called with the provided
 513       * instance. Used by encode_form_data() to preview a widget.
 514  
 515       * @since 5.8.0
 516       *
 517       * @param string    $widget   The widget's PHP class name (see class-wp-widget.php).
 518       * @param array     $instance Widget instance settings.
 519       * @return string
 520       */
 521  	private function get_widget_preview( $widget, $instance ) {
 522          ob_start();
 523          the_widget( $widget, $instance );
 524          return ob_get_clean();
 525      }
 526  
 527      /**
 528       * Returns the output of WP_Widget::form() when called with the provided
 529       * instance. Used by encode_form_data() to preview a widget's form.
 530       *
 531       * @since 5.8.0
 532       *
 533       * @param WP_Widget $widget_object Widget object to call widget() on.
 534       * @param array     $instance Widget instance settings.
 535       * @return string
 536       */
 537  	private function get_widget_form( $widget_object, $instance ) {
 538          ob_start();
 539  
 540          /** This filter is documented in wp-includes/class-wp-widget.php */
 541          $instance = apply_filters(
 542              'widget_form_callback',
 543              $instance,
 544              $widget_object
 545          );
 546  
 547          if ( false !== $instance ) {
 548              $return = $widget_object->form( $instance );
 549  
 550              /** This filter is documented in wp-includes/class-wp-widget.php */
 551              do_action_ref_array(
 552                  'in_widget_form',
 553                  array( &$widget_object, &$return, $instance )
 554              );
 555          }
 556  
 557          return ob_get_clean();
 558      }
 559  
 560      /**
 561       * Retrieves the query params for collections.
 562       *
 563       * @since 5.8.0
 564       *
 565       * @return array Collection parameters.
 566       */
 567  	public function get_collection_params() {
 568          return array(
 569              'context' => $this->get_context_param( array( 'default' => 'view' ) ),
 570          );
 571      }
 572  }


Generated: Mon Sep 20 01:00:04 2021 Cross-referenced by PHPXref 0.7.1