home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / rest-api / endpoints / class-wp-rest-settings-controller.php < prev    next >
Encoding:
PHP Script  |  2017-10-24  |  9.9 KB  |  335 lines

  1. <?php
  2. /**
  3.  * REST API: WP_REST_Settings_Controller class
  4.  *
  5.  * @package WordPress
  6.  * @subpackage REST_API
  7.  * @since 4.7.0
  8.  */
  9.  
  10. /**
  11.  * Core class used to manage a site's settings via the REST API.
  12.  *
  13.  * @since 4.7.0
  14.  *
  15.  * @see WP_REST_Controller
  16.  */
  17. class WP_REST_Settings_Controller extends WP_REST_Controller {
  18.  
  19.     /**
  20.      * Constructor.
  21.      *
  22.      * @since 4.7.0
  23.      */
  24.     public function __construct() {
  25.         $this->namespace = 'wp/v2';
  26.         $this->rest_base = 'settings';
  27.     }
  28.  
  29.     /**
  30.      * Registers the routes for the objects of the controller.
  31.      *
  32.      * @since 4.7.0
  33.      *
  34.      * @see register_rest_route()
  35.      */
  36.     public function register_routes() {
  37.  
  38.         register_rest_route( $this->namespace, '/' . $this->rest_base, array(
  39.             array(
  40.                 'methods'             => WP_REST_Server::READABLE,
  41.                 'callback'            => array( $this, 'get_item' ),
  42.                 'args'                => array(),
  43.                 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  44.             ),
  45.             array(
  46.                 'methods'             => WP_REST_Server::EDITABLE,
  47.                 'callback'            => array( $this, 'update_item' ),
  48.                 'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
  49.                 'permission_callback' => array( $this, 'get_item_permissions_check' ),
  50.             ),
  51.             'schema' => array( $this, 'get_public_item_schema' ),
  52.         ) );
  53.  
  54.     }
  55.  
  56.     /**
  57.      * Checks if a given request has access to read and manage settings.
  58.      *
  59.      * @since 4.7.0
  60.      *
  61.      * @param WP_REST_Request $request Full details about the request.
  62.      * @return bool True if the request has read access for the item, otherwise false.
  63.      */
  64.     public function get_item_permissions_check( $request ) {
  65.         return current_user_can( 'manage_options' );
  66.     }
  67.  
  68.     /**
  69.      * Retrieves the settings.
  70.      *
  71.      * @since 4.7.0
  72.      *
  73.      * @param WP_REST_Request $request Full details about the request.
  74.      * @return array|WP_Error Array on success, or WP_Error object on failure.
  75.      */
  76.     public function get_item( $request ) {
  77.         $options  = $this->get_registered_options();
  78.         $response = array();
  79.  
  80.         foreach ( $options as $name => $args ) {
  81.             /**
  82.              * Filters the value of a setting recognized by the REST API.
  83.              *
  84.              * Allow hijacking the setting value and overriding the built-in behavior by returning a
  85.              * non-null value.  The returned value will be presented as the setting value instead.
  86.              *
  87.              * @since 4.7.0
  88.              *
  89.              * @param mixed  $result Value to use for the requested setting. Can be a scalar
  90.              *                       matching the registered schema for the setting, or null to
  91.              *                       follow the default get_option() behavior.
  92.              * @param string $name   Setting name (as shown in REST API responses).
  93.              * @param array  $args   Arguments passed to register_setting() for this setting.
  94.              */
  95.             $response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
  96.  
  97.             if ( is_null( $response[ $name ] ) ) {
  98.                 // Default to a null value as "null" in the response means "not set".
  99.                 $response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
  100.             }
  101.  
  102.             /*
  103.              * Because get_option() is lossy, we have to
  104.              * cast values to the type they are registered with.
  105.              */
  106.             $response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
  107.         }
  108.  
  109.         return $response;
  110.     }
  111.  
  112.     /**
  113.      * Prepares a value for output based off a schema array.
  114.      *
  115.      * @since 4.7.0
  116.      *
  117.      * @param mixed $value  Value to prepare.
  118.      * @param array $schema Schema to match.
  119.      * @return mixed The prepared value.
  120.      */
  121.     protected function prepare_value( $value, $schema ) {
  122.         // If the value is not valid by the schema, set the value to null. Null
  123.         // values are specifcally non-destructive so this will not cause overwriting
  124.         // the current invalid value to null.
  125.         if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
  126.             return null;
  127.         }
  128.         return rest_sanitize_value_from_schema( $value, $schema );
  129.     }
  130.  
  131.     /**
  132.      * Updates settings for the settings object.
  133.      *
  134.      * @since 4.7.0
  135.      *
  136.      * @param WP_REST_Request $request Full details about the request.
  137.      * @return array|WP_Error Array on success, or error object on failure.
  138.      */
  139.     public function update_item( $request ) {
  140.         $options = $this->get_registered_options();
  141.  
  142.         $params  = $request->get_params();
  143.  
  144.         foreach ( $options as $name => $args ) {
  145.             if ( ! array_key_exists( $name, $params ) ) {
  146.                 continue;
  147.             }
  148.  
  149.             /**
  150.              * Filters whether to preempt a setting value update.
  151.              *
  152.              * Allows hijacking the setting update logic and overriding the built-in behavior by
  153.              * returning true.
  154.              *
  155.              * @since 4.7.0
  156.              *
  157.              * @param bool   $result Whether to override the default behavior for updating the
  158.              *                       value of a setting.
  159.              * @param string $name   Setting name (as shown in REST API responses).
  160.              * @param mixed  $value  Updated setting value.
  161.              * @param array  $args   Arguments passed to register_setting() for this setting.
  162.              */
  163.             $updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
  164.  
  165.             if ( $updated ) {
  166.                 continue;
  167.             }
  168.  
  169.             /*
  170.              * A null value for an option would have the same effect as
  171.              * deleting the option from the database, and relying on the
  172.              * default value.
  173.              */
  174.             if ( is_null( $request[ $name ] ) ) {
  175.                 /*
  176.                  * A null value is returned in the response for any option
  177.                  * that has a non-scalar value.
  178.                  *
  179.                  * To protect clients from accidentally including the null
  180.                  * values from a response object in a request, we do not allow
  181.                  * options with values that don't pass validation to be updated to null.
  182.                  * Without this added protection a client could mistakenly
  183.                  * delete all options that have invalid values from the
  184.                  * database.
  185.                  */
  186.                 if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) {
  187.                     return new WP_Error(
  188.                         'rest_invalid_stored_value', sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), array( 'status' => 500 )
  189.                     );
  190.                 }
  191.  
  192.                 delete_option( $args['option_name'] );
  193.             } else {
  194.                 update_option( $args['option_name'], $request[ $name ] );
  195.             }
  196.         }
  197.  
  198.         return $this->get_item( $request );
  199.     }
  200.  
  201.     /**
  202.      * Retrieves all of the registered options for the Settings API.
  203.      *
  204.      * @since 4.7.0
  205.      *
  206.      * @return array Array of registered options.
  207.      */
  208.     protected function get_registered_options() {
  209.         $rest_options = array();
  210.  
  211.         foreach ( get_registered_settings() as $name => $args ) {
  212.             if ( empty( $args['show_in_rest'] ) ) {
  213.                 continue;
  214.             }
  215.  
  216.             $rest_args = array();
  217.  
  218.             if ( is_array( $args['show_in_rest'] ) ) {
  219.                 $rest_args = $args['show_in_rest'];
  220.             }
  221.  
  222.             $defaults = array(
  223.                 'name'   => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
  224.                 'schema' => array(),
  225.             );
  226.  
  227.             $rest_args = array_merge( $defaults, $rest_args );
  228.  
  229.             $default_schema = array(
  230.                 'type'        => empty( $args['type'] ) ? null : $args['type'],
  231.                 'description' => empty( $args['description'] ) ? '' : $args['description'],
  232.                 'default'     => isset( $args['default'] ) ? $args['default'] : null,
  233.             );
  234.  
  235.             $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
  236.             $rest_args['option_name'] = $name;
  237.  
  238.             // Skip over settings that don't have a defined type in the schema.
  239.             if ( empty( $rest_args['schema']['type'] ) ) {
  240.                 continue;
  241.             }
  242.  
  243.             /*
  244.              * Whitelist the supported types for settings, as we don't want invalid types
  245.              * to be updated with arbitrary values that we can't do decent sanitizing for.
  246.              */
  247.             if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) {
  248.                 continue;
  249.             }
  250.  
  251.             $rest_args['schema'] = $this->set_additional_properties_to_false( $rest_args['schema'] );
  252.  
  253.             $rest_options[ $rest_args['name'] ] = $rest_args;
  254.         }
  255.  
  256.         return $rest_options;
  257.     }
  258.  
  259.     /**
  260.      * Retrieves the site setting schema, conforming to JSON Schema.
  261.      *
  262.      * @since 4.7.0
  263.      *
  264.      * @return array Item schema data.
  265.      */
  266.     public function get_item_schema() {
  267.         $options = $this->get_registered_options();
  268.  
  269.         $schema = array(
  270.             '$schema'    => 'http://json-schema.org/draft-04/schema#',
  271.             'title'      => 'settings',
  272.             'type'       => 'object',
  273.             'properties' => array(),
  274.         );
  275.  
  276.         foreach ( $options as $option_name => $option ) {
  277.             $schema['properties'][ $option_name ] = $option['schema'];
  278.             $schema['properties'][ $option_name ]['arg_options'] = array(
  279.                 'sanitize_callback' => array( $this, 'sanitize_callback' ),
  280.             );
  281.         }
  282.  
  283.         return $this->add_additional_fields_schema( $schema );
  284.     }
  285.  
  286.     /**
  287.      * Custom sanitize callback used for all options to allow the use of 'null'.
  288.      *
  289.      * By default, the schema of settings will throw an error if a value is set to
  290.      * `null` as it's not a valid value for something like "type => string". We
  291.      * provide a wrapper sanitizer to whitelist the use of `null`.
  292.      *
  293.      * @since 4.7.0
  294.      *
  295.      * @param  mixed           $value   The value for the setting.
  296.      * @param  WP_REST_Request $request The request object.
  297.      * @param  string          $param   The parameter name.
  298.      * @return mixed|WP_Error
  299.      */
  300.     public function sanitize_callback( $value, $request, $param ) {
  301.         if ( is_null( $value ) ) {
  302.             return $value;
  303.         }
  304.         return rest_parse_request_arg( $value, $request, $param );
  305.     }
  306.  
  307.     /**
  308.      * Recursively add additionalProperties = false to all objects in a schema.
  309.      *
  310.      * This is need to restrict properties of objects in settings values to only
  311.      * registered items, as the REST API will allow additional properties by
  312.      * default.
  313.      *
  314.      * @since 4.9.0
  315.      *
  316.      * @param array $schema The schema array.
  317.      * @return array
  318.      */
  319.     protected function set_additional_properties_to_false( $schema ) {
  320.         switch ( $schema['type'] ) {
  321.             case 'object':
  322.                 foreach ( $schema['properties'] as $key => $child_schema ) {
  323.                     $schema['properties'][ $key ] = $this->set_additional_properties_to_false( $child_schema );
  324.                 }
  325.                 $schema['additionalProperties'] = false;
  326.                 break;
  327.             case 'array':
  328.                 $schema['items'] = $this->set_additional_properties_to_false( $schema['items'] );
  329.                 break;
  330.         }
  331.  
  332.         return $schema;
  333.     }
  334. }
  335.