[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-customize-setting.php (source)

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


Generated : Mon Nov 9 08:20:01 2026 Cross-referenced by PHPXref