home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-customize-setting.php < prev    next >
Encoding:
PHP Script  |  2017-07-26  |  27.5 KB  |  959 lines

  1. <?php
  2. /**
  3.  * WordPress Customize Setting classes
  4.  *
  5.  * @package WordPress
  6.  * @subpackage Customize
  7.  * @since 3.4.0
  8.  */
  9.  
  10. /**
  11.  * Customize Setting class.
  12.  *
  13.  * Handles saving and sanitizing of settings.
  14.  *
  15.  * @since 3.4.0
  16.  *
  17.  * @see WP_Customize_Manager
  18.  */
  19. class WP_Customize_Setting {
  20.     /**
  21.      * Customizer bootstrap instance.
  22.      *
  23.      * @since 3.4.0
  24.      * @var WP_Customize_Manager
  25.      */
  26.     public $manager;
  27.  
  28.     /**
  29.      * Unique string identifier for the setting.
  30.      *
  31.      * @since 3.4.0
  32.      * @var string
  33.      */
  34.     public $id;
  35.  
  36.     /**
  37.      * Type of customize settings.
  38.      *
  39.      * @since 3.4.0
  40.      * @var string
  41.      */
  42.     public $type = 'theme_mod';
  43.  
  44.     /**
  45.      * Capability required to edit this setting.
  46.      *
  47.      * @since 3.4.0
  48.      * @var string|array
  49.      */
  50.     public $capability = 'edit_theme_options';
  51.  
  52.     /**
  53.      * Feature a theme is required to support to enable this setting.
  54.      *
  55.      * @since 3.4.0
  56.      * @var string
  57.      */
  58.     public $theme_supports = '';
  59.  
  60.     /**
  61.      * The default value for the setting.
  62.      *
  63.      * @since 3.4.0
  64.      * @var string
  65.      */
  66.     public $default = '';
  67.  
  68.     /**
  69.      * Options for rendering the live preview of changes in Theme Customizer.
  70.      *
  71.      * Set this value to 'postMessage' to enable a custom Javascript handler to render changes to this setting
  72.      * as opposed to reloading the whole page.
  73.      *
  74.      * @link https://developer.wordpress.org/themes/customize-api
  75.      *
  76.      * @since 3.4.0
  77.      * @var string
  78.      */
  79.     public $transport = 'refresh';
  80.  
  81.     /**
  82.      * Server-side validation callback for the setting's value.
  83.      *
  84.      * @since 4.6.0
  85.      * @var callable
  86.      */
  87.     public $validate_callback = '';
  88.  
  89.     /**
  90.      * Callback to filter a Customize setting value in un-slashed form.
  91.      *
  92.      * @since 3.4.0
  93.      * @var callable
  94.      */
  95.     public $sanitize_callback = '';
  96.  
  97.     /**
  98.      * Callback to convert a Customize PHP setting value to a value that is JSON serializable.
  99.      *
  100.      * @since 3.4.0
  101.      * @var string
  102.      */
  103.     public $sanitize_js_callback = '';
  104.  
  105.     /**
  106.      * Whether or not the setting is initially dirty when created.
  107.      *
  108.      * This is used to ensure that a setting will be sent from the pane to the
  109.      * preview when loading the Customizer. Normally a setting only is synced to
  110.      * the preview if it has been changed. This allows the setting to be sent
  111.      * from the start.
  112.      *
  113.      * @since 4.2.0
  114.      * @var bool
  115.      */
  116.     public $dirty = false;
  117.  
  118.     /**
  119.      * ID Data.
  120.      *
  121.      * @since 3.4.0
  122.      * @var array
  123.      */
  124.     protected $id_data = array();
  125.  
  126.     /**
  127.      * Whether or not preview() was called.
  128.      *
  129.      * @since 4.4.0
  130.      * @var bool
  131.      */
  132.     protected $is_previewed = false;
  133.  
  134.     /**
  135.      * Cache of multidimensional values to improve performance.
  136.      *
  137.      * @since 4.4.0
  138.      * @static
  139.      * @var array
  140.      */
  141.     protected static $aggregated_multidimensionals = array();
  142.  
  143.     /**
  144.      * Whether the multidimensional setting is aggregated.
  145.      *
  146.      * @since 4.4.0
  147.      * @var bool
  148.      */
  149.     protected $is_multidimensional_aggregated = false;
  150.  
  151.     /**
  152.      * Constructor.
  153.      *
  154.      * Any supplied $args override class property defaults.
  155.      *
  156.      * @since 3.4.0
  157.      *
  158.      * @param WP_Customize_Manager $manager
  159.      * @param string               $id      An specific ID of the setting. Can be a
  160.      *                                      theme mod or option name.
  161.      * @param array                $args    Setting arguments.
  162.      */
  163.     public function __construct( $manager, $id, $args = array() ) {
  164.         $keys = array_keys( get_object_vars( $this ) );
  165.         foreach ( $keys as $key ) {
  166.             if ( isset( $args[ $key ] ) ) {
  167.                 $this->$key = $args[ $key ];
  168.             }
  169.         }
  170.  
  171.         $this->manager = $manager;
  172.         $this->id = $id;
  173.  
  174.         // Parse the ID for array keys.
  175.         $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
  176.         $this->id_data['base'] = array_shift( $this->id_data['keys'] );
  177.  
  178.         // Rebuild the ID.
  179.         $this->id = $this->id_data[ 'base' ];
  180.         if ( ! empty( $this->id_data[ 'keys' ] ) ) {
  181.             $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
  182.         }
  183.  
  184.         if ( $this->validate_callback ) {
  185.             add_filter( "customize_validate_{$this->id}", $this->validate_callback, 10, 3 );
  186.         }
  187.         if ( $this->sanitize_callback ) {
  188.             add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
  189.         }
  190.         if ( $this->sanitize_js_callback ) {
  191.             add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
  192.         }
  193.  
  194.         if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
  195.             // Other setting types can opt-in to aggregate multidimensional explicitly.
  196.             $this->aggregate_multidimensional();
  197.  
  198.             // Allow option settings to indicate whether they should be autoloaded.
  199.             if ( 'option' === $this->type && isset( $args['autoload'] ) ) {
  200.                 self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload'];
  201.             }
  202.         }
  203.     }
  204.  
  205.     /**
  206.      * Get parsed ID data for multidimensional setting.
  207.      *
  208.      * @since 4.4.0
  209.      *
  210.      * @return array {
  211.      *     ID data for multidimensional setting.
  212.      *
  213.      *     @type string $base ID base
  214.      *     @type array  $keys Keys for multidimensional array.
  215.      * }
  216.      */
  217.     final public function id_data() {
  218.         return $this->id_data;
  219.     }
  220.  
  221.     /**
  222.      * Set up the setting for aggregated multidimensional values.
  223.      *
  224.      * When a multidimensional setting gets aggregated, all of its preview and update
  225.      * calls get combined into one call, greatly improving performance.
  226.      *
  227.      * @since 4.4.0
  228.      */
  229.     protected function aggregate_multidimensional() {
  230.         $id_base = $this->id_data['base'];
  231.         if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) {
  232.             self::$aggregated_multidimensionals[ $this->type ] = array();
  233.         }
  234.         if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) {
  235.             self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array(
  236.                 'previewed_instances'       => array(), // Calling preview() will add the $setting to the array.
  237.                 'preview_applied_instances' => array(), // Flags for which settings have had their values applied.
  238.                 'root_value'                => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls.
  239.             );
  240.         }
  241.  
  242.         if ( ! empty( $this->id_data['keys'] ) ) {
  243.             // Note the preview-applied flag is cleared at priority 9 to ensure it is cleared before a deferred-preview runs.
  244.             add_action( "customize_post_value_set_{$this->id}", array( $this, '_clear_aggregated_multidimensional_preview_applied_flag' ), 9 );
  245.             $this->is_multidimensional_aggregated = true;
  246.         }
  247.     }
  248.  
  249.     /**
  250.      * Reset `$aggregated_multidimensionals` static variable.
  251.      *
  252.      * This is intended only for use by unit tests.
  253.      *
  254.      * @since 4.5.0
  255.      * @ignore
  256.      */
  257.     static public function reset_aggregated_multidimensionals() {
  258.         self::$aggregated_multidimensionals = array();
  259.     }
  260.  
  261.     /**
  262.      * The ID for the current site when the preview() method was called.
  263.      *
  264.      * @since 4.2.0
  265.      * @var int
  266.      */
  267.     protected $_previewed_blog_id;
  268.  
  269.     /**
  270.      * Return true if the current site is not the same as the previewed site.
  271.      *
  272.      * @since 4.2.0
  273.      *
  274.      * @return bool If preview() has been called.
  275.      */
  276.     public function is_current_blog_previewed() {
  277.         if ( ! isset( $this->_previewed_blog_id ) ) {
  278.             return false;
  279.         }
  280.         return ( get_current_blog_id() === $this->_previewed_blog_id );
  281.     }
  282.  
  283.     /**
  284.      * Original non-previewed value stored by the preview method.
  285.      *
  286.      * @see WP_Customize_Setting::preview()
  287.      * @since 4.1.1
  288.      * @var mixed
  289.      */
  290.     protected $_original_value;
  291.  
  292.     /**
  293.      * Add filters to supply the setting's value when accessed.
  294.      *
  295.      * If the setting already has a pre-existing value and there is no incoming
  296.      * post value for the setting, then this method will short-circuit since
  297.      * there is no change to preview.
  298.      *
  299.      * @since 3.4.0
  300.      * @since 4.4.0 Added boolean return value.
  301.      *
  302.      * @return bool False when preview short-circuits due no change needing to be previewed.
  303.      */
  304.     public function preview() {
  305.         if ( ! isset( $this->_previewed_blog_id ) ) {
  306.             $this->_previewed_blog_id = get_current_blog_id();
  307.         }
  308.  
  309.         // Prevent re-previewing an already-previewed setting.
  310.         if ( $this->is_previewed ) {
  311.             return true;
  312.         }
  313.  
  314.         $id_base = $this->id_data['base'];
  315.         $is_multidimensional = ! empty( $this->id_data['keys'] );
  316.         $multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
  317.  
  318.         /*
  319.          * Check if the setting has a pre-existing value (an isset check),
  320.          * and if doesn't have any incoming post value. If both checks are true,
  321.          * then the preview short-circuits because there is nothing that needs
  322.          * to be previewed.
  323.          */
  324.         $undefined = new stdClass();
  325.         $needs_preview = ( $undefined !== $this->post_value( $undefined ) );
  326.         $value = null;
  327.  
  328.         // Since no post value was defined, check if we have an initial value set.
  329.         if ( ! $needs_preview ) {
  330.             if ( $this->is_multidimensional_aggregated ) {
  331.                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
  332.                 $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
  333.             } else {
  334.                 $default = $this->default;
  335.                 $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
  336.                 $value = $this->value();
  337.                 $this->default = $default;
  338.             }
  339.             $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
  340.         }
  341.  
  342.         // If the setting does not need previewing now, defer to when it has a value to preview.
  343.         if ( ! $needs_preview ) {
  344.             if ( ! has_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) ) ) {
  345.                 add_action( "customize_post_value_set_{$this->id}", array( $this, 'preview' ) );
  346.             }
  347.             return false;
  348.         }
  349.  
  350.         switch ( $this->type ) {
  351.             case 'theme_mod' :
  352.                 if ( ! $is_multidimensional ) {
  353.                     add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
  354.                 } else {
  355.                     if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
  356.                         // Only add this filter once for this ID base.
  357.                         add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
  358.                     }
  359.                     self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
  360.                 }
  361.                 break;
  362.             case 'option' :
  363.                 if ( ! $is_multidimensional ) {
  364.                     add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
  365.                 } else {
  366.                     if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
  367.                         // Only add these filters once for this ID base.
  368.                         add_filter( "option_{$id_base}", $multidimensional_filter );
  369.                         add_filter( "default_option_{$id_base}", $multidimensional_filter );
  370.                     }
  371.                     self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
  372.                 }
  373.                 break;
  374.             default :
  375.  
  376.                 /**
  377.                  * Fires when the WP_Customize_Setting::preview() method is called for settings
  378.                  * not handled as theme_mods or options.
  379.                  *
  380.                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
  381.                  *
  382.                  * @since 3.4.0
  383.                  *
  384.                  * @param WP_Customize_Setting $this WP_Customize_Setting instance.
  385.                  */
  386.                 do_action( "customize_preview_{$this->id}", $this );
  387.  
  388.                 /**
  389.                  * Fires when the WP_Customize_Setting::preview() method is called for settings
  390.                  * not handled as theme_mods or options.
  391.                  *
  392.                  * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
  393.                  *
  394.                  * @since 4.1.0
  395.                  *
  396.                  * @param WP_Customize_Setting $this WP_Customize_Setting instance.
  397.                  */
  398.                 do_action( "customize_preview_{$this->type}", $this );
  399.         }
  400.  
  401.         $this->is_previewed = true;
  402.  
  403.         return true;
  404.     }
  405.  
  406.     /**
  407.      * Clear out the previewed-applied flag for a multidimensional-aggregated value whenever its post value is updated.
  408.      *
  409.      * This ensures that the new value will get sanitized and used the next time
  410.      * that `WP_Customize_Setting::_multidimensional_preview_filter()`
  411.      * is called for this setting.
  412.      *
  413.      * @since 4.4.0
  414.      *
  415.      * @see WP_Customize_Manager::set_post_value()
  416.      * @see WP_Customize_Setting::_multidimensional_preview_filter()
  417.      */
  418.     final public function _clear_aggregated_multidimensional_preview_applied_flag() {
  419.         unset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['preview_applied_instances'][ $this->id ] );
  420.     }
  421.  
  422.     /**
  423.      * Callback function to filter non-multidimensional theme mods and options.
  424.      *
  425.      * If switch_to_blog() was called after the preview() method, and the current
  426.      * site is now not the same site, then this method does a no-op and returns
  427.      * the original value.
  428.      *
  429.      * @since 3.4.0
  430.      *
  431.      * @param mixed $original Old value.
  432.      * @return mixed New or old value.
  433.      */
  434.     public function _preview_filter( $original ) {
  435.         if ( ! $this->is_current_blog_previewed() ) {
  436.             return $original;
  437.         }
  438.  
  439.         $undefined = new stdClass(); // Symbol hack.
  440.         $post_value = $this->post_value( $undefined );
  441.         if ( $undefined !== $post_value ) {
  442.             $value = $post_value;
  443.         } else {
  444.             /*
  445.              * Note that we don't use $original here because preview() will
  446.              * not add the filter in the first place if it has an initial value
  447.              * and there is no post value.
  448.              */
  449.             $value = $this->default;
  450.         }
  451.         return $value;
  452.     }
  453.  
  454.     /**
  455.      * Callback function to filter multidimensional theme mods and options.
  456.      *
  457.      * For all multidimensional settings of a given type, the preview filter for
  458.      * the first setting previewed will be used to apply the values for the others.
  459.      *
  460.      * @since 4.4.0
  461.      *
  462.      * @see WP_Customize_Setting::$aggregated_multidimensionals
  463.      * @param mixed $original Original root value.
  464.      * @return mixed New or old value.
  465.      */
  466.     final public function _multidimensional_preview_filter( $original ) {
  467.         if ( ! $this->is_current_blog_previewed() ) {
  468.             return $original;
  469.         }
  470.  
  471.         $id_base = $this->id_data['base'];
  472.  
  473.         // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
  474.         if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
  475.             return $original;
  476.         }
  477.  
  478.         foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
  479.             // Skip applying previewed value for any settings that have already been applied.
  480.             if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
  481.                 continue;
  482.             }
  483.  
  484.             // Do the replacements of the posted/default sub value into the root value.
  485.             $value = $previewed_setting->post_value( $previewed_setting->default );
  486.             $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
  487.             $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
  488.             self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
  489.  
  490.             // Mark this setting having been applied so that it will be skipped when the filter is called again.
  491.             self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
  492.         }
  493.  
  494.         return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
  495.     }
  496.  
  497.     /**
  498.      * Checks user capabilities and theme supports, and then saves
  499.      * the value of the setting.
  500.      *
  501.      * @since 3.4.0
  502.      *
  503.      * @return false|void False if cap check fails or value isn't set or is invalid.
  504.      */
  505.     final public function save() {
  506.         $value = $this->post_value();
  507.  
  508.         if ( ! $this->check_capabilities() || ! isset( $value ) ) {
  509.             return false;
  510.         }
  511.  
  512.         $id_base = $this->id_data['base'];
  513.  
  514.         /**
  515.          * Fires when the WP_Customize_Setting::save() method is called.
  516.          *
  517.          * The dynamic portion of the hook name, `$id_base` refers to
  518.          * the base slug of the setting name.
  519.          *
  520.          * @since 3.4.0
  521.          *
  522.          * @param WP_Customize_Setting $this WP_Customize_Setting instance.
  523.          */
  524.         do_action( "customize_save_{$id_base}", $this );
  525.  
  526.         $this->update( $value );
  527.     }
  528.  
  529.     /**
  530.      * Fetch and sanitize the $_POST value for the setting.
  531.      *
  532.      * During a save request prior to save, post_value() provides the new value while value() does not.
  533.      *
  534.      * @since 3.4.0
  535.      *
  536.      * @param mixed $default A default value which is used as a fallback. Default is null.
  537.      * @return mixed The default value on failure, otherwise the sanitized and validated value.
  538.      */
  539.     final public function post_value( $default = null ) {
  540.         return $this->manager->post_value( $this, $default );
  541.     }
  542.  
  543.     /**
  544.      * Sanitize an input.
  545.      *
  546.      * @since 3.4.0
  547.      *
  548.      * @param string|array $value    The value to sanitize.
  549.      * @return string|array|null|WP_Error Sanitized value, or `null`/`WP_Error` if invalid.
  550.      */
  551.     public function sanitize( $value ) {
  552.  
  553.         /**
  554.          * Filters a Customize setting value in un-slashed form.
  555.          *
  556.          * @since 3.4.0
  557.          *
  558.          * @param mixed                $value Value of the setting.
  559.          * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
  560.          */
  561.         return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
  562.     }
  563.  
  564.     /**
  565.      * Validates an input.
  566.      *
  567.      * @since 4.6.0
  568.      *
  569.      * @see WP_REST_Request::has_valid_params()
  570.      *
  571.      * @param mixed $value Value to validate.
  572.      * @return true|WP_Error True if the input was validated, otherwise WP_Error.
  573.      */
  574.     public function validate( $value ) {
  575.         if ( is_wp_error( $value ) ) {
  576.             return $value;
  577.         }
  578.         if ( is_null( $value ) ) {
  579.             return new WP_Error( 'invalid_value', __( 'Invalid value.' ) );
  580.         }
  581.  
  582.         $validity = new WP_Error();
  583.  
  584.         /**
  585.          * Validates a Customize setting value.
  586.          *
  587.          * Plugins should amend the `$validity` object via its `WP_Error::add()` method.
  588.          *
  589.          * The dynamic portion of the hook name, `$this->ID`, refers to the setting ID.
  590.          *
  591.          * @since 4.6.0
  592.          *
  593.          * @param WP_Error             $validity Filtered from `true` to `WP_Error` when invalid.
  594.          * @param mixed                $value    Value of the setting.
  595.          * @param WP_Customize_Setting $this     WP_Customize_Setting instance.
  596.          */
  597.         $validity = apply_filters( "customize_validate_{$this->id}", $validity, $value, $this );
  598.  
  599.         if ( is_wp_error( $validity ) && empty( $validity->errors ) ) {
  600.             $validity = true;
  601.         }
  602.         return $validity;
  603.     }
  604.  
  605.     /**
  606.      * Get the root value for a setting, especially for multidimensional ones.
  607.      *
  608.      * @since 4.4.0
  609.      *
  610.      * @param mixed $default Value to return if root does not exist.
  611.      * @return mixed
  612.      */
  613.     protected function get_root_value( $default = null ) {
  614.         $id_base = $this->id_data['base'];
  615.         if ( 'option' === $this->type ) {
  616.             return get_option( $id_base, $default );
  617.         } elseif ( 'theme_mod' === $this->type ) {
  618.             return get_theme_mod( $id_base, $default );
  619.         } else {
  620.             /*
  621.              * Any WP_Customize_Setting subclass implementing aggregate multidimensional
  622.              * will need to override this method to obtain the data from the appropriate
  623.              * location.
  624.              */
  625.             return $default;
  626.         }
  627.     }
  628.  
  629.     /**
  630.      * Set the root value for a setting, especially for multidimensional ones.
  631.      *
  632.      * @since 4.4.0
  633.      *
  634.      * @param mixed $value Value to set as root of multidimensional setting.
  635.      * @return bool Whether the multidimensional root was updated successfully.
  636.      */
  637.     protected function set_root_value( $value ) {
  638.         $id_base = $this->id_data['base'];
  639.         if ( 'option' === $this->type ) {
  640.             $autoload = true;
  641.             if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
  642.                 $autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
  643.             }
  644.             return update_option( $id_base, $value, $autoload );
  645.         } elseif ( 'theme_mod' === $this->type ) {
  646.             set_theme_mod( $id_base, $value );
  647.             return true;
  648.         } else {
  649.             /*
  650.              * Any WP_Customize_Setting subclass implementing aggregate multidimensional
  651.              * will need to override this method to obtain the data from the appropriate
  652.              * location.
  653.              */
  654.             return false;
  655.         }
  656.     }
  657.  
  658.     /**
  659.      * Save the value of the setting, using the related API.
  660.      *
  661.      * @since 3.4.0
  662.      *
  663.      * @param mixed $value The value to update.
  664.      * @return bool The result of saving the value.
  665.      */
  666.     protected function update( $value ) {
  667.         $id_base = $this->id_data['base'];
  668.         if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
  669.             if ( ! $this->is_multidimensional_aggregated ) {
  670.                 return $this->set_root_value( $value );
  671.             } else {
  672.                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
  673.                 $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
  674.                 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
  675.                 return $this->set_root_value( $root );
  676.             }
  677.         } else {
  678.             /**
  679.              * Fires when the WP_Customize_Setting::update() method is called for settings
  680.              * not handled as theme_mods or options.
  681.              *
  682.              * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
  683.              *
  684.              * @since 3.4.0
  685.              *
  686.              * @param mixed                $value Value of the setting.
  687.              * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
  688.              */
  689.             do_action( "customize_update_{$this->type}", $value, $this );
  690.  
  691.             return has_action( "customize_update_{$this->type}" );
  692.         }
  693.     }
  694.  
  695.     /**
  696.      * Deprecated method.
  697.      *
  698.      * @since 3.4.0
  699.      * @deprecated 4.4.0 Deprecated in favor of update() method.
  700.      */
  701.     protected function _update_theme_mod() {
  702.         _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
  703.     }
  704.  
  705.     /**
  706.      * Deprecated method.
  707.      *
  708.      * @since 3.4.0
  709.      * @deprecated 4.4.0 Deprecated in favor of update() method.
  710.      */
  711.     protected function _update_option() {
  712.         _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
  713.     }
  714.  
  715.     /**
  716.      * Fetch the value of the setting.
  717.      *
  718.      * @since 3.4.0
  719.      *
  720.      * @return mixed The value.
  721.      */
  722.     public function value() {
  723.         $id_base = $this->id_data['base'];
  724.         $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
  725.  
  726.         if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
  727.  
  728.             // Use post value if previewed and a post value is present.
  729.             if ( $this->is_previewed ) {
  730.                 $value = $this->post_value( null );
  731.                 if ( null !== $value ) {
  732.                     return $value;
  733.                 }
  734.             }
  735.  
  736.             $value = $this->get_root_value( $this->default );
  737.  
  738.             /**
  739.              * Filters a Customize setting value not handled as a theme_mod or option.
  740.              *
  741.              * The dynamic portion of the hook name, `$id_base`, refers to
  742.              * the base slug of the setting name, initialized from `$this->id_data['base']`.
  743.              *
  744.              * For settings handled as theme_mods or options, see those corresponding
  745.              * functions for available hooks.
  746.              *
  747.              * @since 3.4.0
  748.              * @since 4.6.0 Added the `$this` setting instance as the second parameter.
  749.              *
  750.              * @param mixed                $default The setting default value. Default empty.
  751.              * @param WP_Customize_Setting $this    The setting instance.
  752.              */
  753.             $value = apply_filters( "customize_value_{$id_base}", $value, $this );
  754.         } elseif ( $this->is_multidimensional_aggregated ) {
  755.             $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
  756.             $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
  757.  
  758.             // Ensure that the post value is used if the setting is previewed, since preview filters aren't applying on cached $root_value.
  759.             if ( $this->is_previewed ) {
  760.                 $value = $this->post_value( $value );
  761.             }
  762.         } else {
  763.             $value = $this->get_root_value( $this->default );
  764.         }
  765.         return $value;
  766.     }
  767.  
  768.     /**
  769.      * Sanitize the setting's value for use in JavaScript.
  770.      *
  771.      * @since 3.4.0
  772.      *
  773.      * @return mixed The requested escaped value.
  774.      */
  775.     public function js_value() {
  776.  
  777.         /**
  778.          * Filters a Customize setting value for use in JavaScript.
  779.          *
  780.          * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
  781.          *
  782.          * @since 3.4.0
  783.          *
  784.          * @param mixed                $value The setting value.
  785.          * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
  786.          */
  787.         $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
  788.  
  789.         if ( is_string( $value ) )
  790.             return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
  791.  
  792.         return $value;
  793.     }
  794.  
  795.     /**
  796.      * Retrieves the data to export to the client via JSON.
  797.      *
  798.      * @since 4.6.0
  799.      *
  800.      * @return array Array of parameters passed to JavaScript.
  801.      */
  802.     public function json() {
  803.         return array(
  804.             'value'     => $this->js_value(),
  805.             'transport' => $this->transport,
  806.             'dirty'     => $this->dirty,
  807.             'type'      => $this->type,
  808.         );
  809.     }
  810.  
  811.     /**
  812.      * Validate user capabilities whether the theme supports the setting.
  813.      *
  814.      * @since 3.4.0
  815.      *
  816.      * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
  817.      */
  818.     final public function check_capabilities() {
  819.         if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
  820.             return false;
  821.  
  822.         if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
  823.             return false;
  824.  
  825.         return true;
  826.     }
  827.  
  828.     /**
  829.      * Multidimensional helper function.
  830.      *
  831.      * @since 3.4.0
  832.      *
  833.      * @param $root
  834.      * @param $keys
  835.      * @param bool $create Default is false.
  836.      * @return array|void Keys are 'root', 'node', and 'key'.
  837.      */
  838.     final protected function multidimensional( &$root, $keys, $create = false ) {
  839.         if ( $create && empty( $root ) )
  840.             $root = array();
  841.  
  842.         if ( ! isset( $root ) || empty( $keys ) )
  843.             return;
  844.  
  845.         $last = array_pop( $keys );
  846.         $node = &$root;
  847.  
  848.         foreach ( $keys as $key ) {
  849.             if ( $create && ! isset( $node[ $key ] ) )
  850.                 $node[ $key ] = array();
  851.  
  852.             if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
  853.                 return;
  854.  
  855.             $node = &$node[ $key ];
  856.         }
  857.  
  858.         if ( $create ) {
  859.             if ( ! is_array( $node ) ) {
  860.                 // account for an array overriding a string or object value
  861.                 $node = array();
  862.             }
  863.             if ( ! isset( $node[ $last ] ) ) {
  864.                 $node[ $last ] = array();
  865.             }
  866.         }
  867.  
  868.         if ( ! isset( $node[ $last ] ) )
  869.             return;
  870.  
  871.         return array(
  872.             'root' => &$root,
  873.             'node' => &$node,
  874.             'key'  => $last,
  875.         );
  876.     }
  877.  
  878.     /**
  879.      * Will attempt to replace a specific value in a multidimensional array.
  880.      *
  881.      * @since 3.4.0
  882.      *
  883.      * @param $root
  884.      * @param $keys
  885.      * @param mixed $value The value to update.
  886.      * @return mixed
  887.      */
  888.     final protected function multidimensional_replace( $root, $keys, $value ) {
  889.         if ( ! isset( $value ) )
  890.             return $root;
  891.         elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
  892.             return $value;
  893.  
  894.         $result = $this->multidimensional( $root, $keys, true );
  895.  
  896.         if ( isset( $result ) )
  897.             $result['node'][ $result['key'] ] = $value;
  898.  
  899.         return $root;
  900.     }
  901.  
  902.     /**
  903.      * Will attempt to fetch a specific value from a multidimensional array.
  904.      *
  905.      * @since 3.4.0
  906.      *
  907.      * @param $root
  908.      * @param $keys
  909.      * @param mixed $default A default value which is used as a fallback. Default is null.
  910.      * @return mixed The requested value or the default value.
  911.      */
  912.     final protected function multidimensional_get( $root, $keys, $default = null ) {
  913.         if ( empty( $keys ) ) // If there are no keys, test the root.
  914.             return isset( $root ) ? $root : $default;
  915.  
  916.         $result = $this->multidimensional( $root, $keys );
  917.         return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
  918.     }
  919.  
  920.     /**
  921.      * Will attempt to check if a specific value in a multidimensional array is set.
  922.      *
  923.      * @since 3.4.0
  924.      *
  925.      * @param $root
  926.      * @param $keys
  927.      * @return bool True if value is set, false if not.
  928.      */
  929.     final protected function multidimensional_isset( $root, $keys ) {
  930.         $result = $this->multidimensional_get( $root, $keys );
  931.         return isset( $result );
  932.     }
  933. }
  934.  
  935. /**
  936.  * WP_Customize_Filter_Setting class.
  937.  */
  938. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-filter-setting.php' );
  939.  
  940. /**
  941.  * WP_Customize_Header_Image_Setting class.
  942.  */
  943. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-header-image-setting.php' );
  944.  
  945. /**
  946.  * WP_Customize_Background_Image_Setting class.
  947.  */
  948. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-background-image-setting.php' );
  949.  
  950. /**
  951.  * WP_Customize_Nav_Menu_Item_Setting class.
  952.  */
  953. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-item-setting.php' );
  954.  
  955. /**
  956.  * WP_Customize_Nav_Menu_Setting class.
  957.  */
  958. require_once( ABSPATH . WPINC . '/customize/class-wp-customize-nav-menu-setting.php' );
  959.