| 142 | } |
| 143 | |
| 144 | if ( 'option' === $this->type || 'theme_mod' === $this->type ) { |
| 145 | // Other setting types can opt-in to aggregate multidimensional explicitly. |
| 146 | $this->aggregate_multidimensional(); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Get parsed ID data for multidimensional setting. |
| 152 | * |
| 153 | * @since 4.4.0 |
| 154 | * @access public |
| 155 | * |
| 156 | * @return array { |
| 157 | * ID data for multidimensional setting. |
| 158 | * |
| 159 | * @type string $base ID base |
| 160 | * @type array $keys Keys for multidimensional array. |
| 161 | * } |
| 162 | */ |
| 163 | final public function id_data() { |
| 164 | return $this->id_data; |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Set up the setting for aggregated multidimensional values. |
| 169 | * |
| 170 | * When a multidimensional setting gets aggregated, all of its preview and update |
| 171 | * calls get combined into one call, greatly improving performance. |
| 172 | * |
| 173 | * @since 4.4.0 |
| 174 | * @access protected |
| 175 | */ |
| 176 | protected function aggregate_multidimensional() { |
| 177 | if ( empty( $this->id_data['keys'] ) ) { |
| 178 | return; |
| 179 | } |
| 180 | |
| 181 | $id_base = $this->id_data['base']; |
| 182 | if ( ! isset( self::$aggregated_multidimensionals[ $this->type ] ) ) { |
| 183 | self::$aggregated_multidimensionals[ $this->type ] = array(); |
| 184 | } |
| 185 | if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ] ) ) { |
| 186 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ] = array( |
| 187 | 'previewed_instances' => array(), // Calling preview() will add the $setting to the array. |
| 188 | 'preview_applied_instances' => array(), // Flags for which settings have had their values applied. |
| 189 | 'previewed_value' => null, // Root value which has multidimensional replacements applied for each previewed instance. |
| 190 | 'updated_value' => null, // Each call to update() will do the multidimensional replacements on $this->get_root_value() |
| 191 | ); |
| 192 | } |
| 193 | $this->is_multidimensional_aggregated = true; |
171 | | add_filter( 'theme_mod_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
| 248 | if ( ! $is_multidimensional ) { |
| 249 | add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) ); |
| 250 | } else { |
| 251 | if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
| 252 | // Grab the initial value upon which the other settings will do multidimensional replacements. |
| 253 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_value'] = $this->get_root_value(); |
| 254 | |
| 255 | add_filter( "theme_mod_{$id_base}", $multidimensional_filter ); |
| 256 | } |
| 257 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; |
| 258 | } |
174 | | if ( empty( $this->id_data[ 'keys' ] ) ) |
175 | | add_filter( 'pre_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
176 | | else { |
177 | | add_filter( 'option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
178 | | add_filter( 'default_option_' . $this->id_data[ 'base' ], array( $this, '_preview_filter' ) ); |
| 261 | if ( ! $is_multidimensional ) { |
| 262 | add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) ); |
| 263 | } else { |
| 264 | if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
| 265 | // Grab the initial value upon which the other settings will do multidimensional replacements. |
| 266 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_value'] = $this->get_root_value(); |
| 267 | |
| 268 | add_filter( "option_{$id_base}", $multidimensional_filter ); |
| 269 | add_filter( "default_option_{$id_base}", $multidimensional_filter ); |
| 270 | } |
| 271 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this; |
235 | | return $this->multidimensional_replace( $original, $this->id_data['keys'], $value ); |
| 329 | /** |
| 330 | * Callback function to filter multidimensional theme mods and options. |
| 331 | * |
| 332 | * For all multidimensional settings of a given type, the preview filter for |
| 333 | * the first setting previewed will be used to apply the values for the others. |
| 334 | * |
| 335 | * @since 4.4.0 |
| 336 | * @access public |
| 337 | * |
| 338 | * @see WP_Customize_Setting::$aggregated_multidimensionals |
| 339 | * @param mixed $original Original root value. |
| 340 | * @return mixed New or old value. |
| 341 | */ |
| 342 | public function _multidimensional_preview_filter( $original ) { |
| 343 | if ( ! $this->is_current_blog_previewed() ) { |
| 344 | return $original; |
| 345 | } |
| 346 | |
| 347 | $id_base = $this->id_data['base']; |
| 348 | |
| 349 | // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value. |
| 350 | if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) { |
| 351 | return $original; |
| 352 | } |
| 353 | |
| 354 | $undefined = new stdClass(); // symbol hack |
| 355 | |
| 356 | foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) { |
| 357 | // Skip applying previewed value for any settings that have already been applied. |
| 358 | if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) { |
| 359 | continue; |
| 360 | } |
| 361 | |
| 362 | // Skip if no post value for this setting is present. |
| 363 | $post_value = $this->post_value( $undefined ); |
| 364 | if ( $undefined === $post_value ) { |
| 365 | continue; |
| 366 | } |
| 367 | |
| 368 | // Do the replacements of the posted sub value into the root value |
| 369 | $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_value']; |
| 370 | $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $post_value ); |
| 371 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_value'] = $root; |
| 372 | |
| 373 | // Mark this setting having been applied so that it will be skipped when the filter is called again. |
| 374 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true; |
| 375 | } |
| 376 | |
| 377 | return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_value']; |
| 444 | * Get the root value for a setting, especially for multidimensional ones. |
| 445 | * |
| 446 | * @since 4.4.0 |
| 447 | * @access protected |
| 448 | * @return mixed |
| 449 | */ |
| 450 | protected function get_root_value() { |
| 451 | $id_base = $this->id_data['base']; |
| 452 | if ( 'option' === $this->type ) { |
| 453 | return get_option( $id_base ); |
| 454 | } else if ( 'theme_mod' ) { |
| 455 | return get_theme_mod( $id_base ); |
| 456 | } else { |
| 457 | /* |
| 458 | * Any WP_Customize_Setting subclass implementing aggregate multidimensional |
| 459 | * will need to override this method to obtain the data from the appropriate |
| 460 | * location. |
| 461 | */ |
| 462 | return null; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | /** |
| 467 | * Set the root value for a setting, especially for multidimensional ones. |
| 468 | * |
| 469 | * @since 4.4.0 |
| 470 | * @access protected |
| 471 | * |
| 472 | * @param mixed $value Value to set as root of multidimensional setting. |
| 473 | * @return bool Whether the multidimensional root was updated successfully. |
| 474 | */ |
| 475 | protected function set_root_value( $value ) { |
| 476 | $id_base = $this->id_data['base']; |
| 477 | if ( 'option' === $this->type ) { |
| 478 | return update_option( $id_base, $value ); |
| 479 | } else if ( 'theme_mod' ) { |
| 480 | set_theme_mod( $id_base, $value ); |
| 481 | return true; |
| 482 | } else { |
| 483 | /* |
| 484 | * Any WP_Customize_Setting subclass implementing aggregate multidimensional |
| 485 | * will need to override this method to obtain the data from the appropriate |
| 486 | * location. |
| 487 | */ |
| 488 | return false; |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | /** |
310 | | switch( $this->type ) { |
311 | | case 'theme_mod' : |
312 | | return $this->_update_theme_mod( $value ); |
313 | | |
314 | | case 'option' : |
315 | | return $this->_update_option( $value ); |
316 | | |
317 | | default : |
318 | | |
319 | | /** |
320 | | * Fires when the {@see WP_Customize_Setting::update()} method is called for settings |
321 | | * not handled as theme_mods or options. |
322 | | * |
323 | | * The dynamic portion of the hook name, `$this->type`, refers to the type of setting. |
324 | | * |
325 | | * @since 3.4.0 |
326 | | * |
327 | | * @param mixed $value Value of the setting. |
328 | | * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
329 | | */ |
330 | | return do_action( 'customize_update_' . $this->type, $value, $this ); |
| 501 | $id_base = $this->id_data['base']; |
| 502 | if ( 'option' === $this->type || 'theme_mod' === $this->type ) { |
| 503 | $is_multidimensional = ! empty( $this->id_data['keys'] ); |
| 504 | if ( ! $is_multidimensional ) { |
| 505 | return $this->set_root_value( $value ); |
| 506 | } else { |
| 507 | if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['updated_value'] ) ) { |
| 508 | $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['updated_value']; |
| 509 | } else { |
| 510 | $root = $this->get_root_value(); |
| 511 | } |
| 512 | $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value ); |
| 513 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['updated_value'] = $root; |
| 514 | } |
| 515 | } else { |
| 516 | /** |
| 517 | * Fires when the {@see WP_Customize_Setting::update()} method is called for settings |
| 518 | * not handled as theme_mods or options. |
| 519 | * |
| 520 | * The dynamic portion of the hook name, `$this->type`, refers to the type of setting. |
| 521 | * |
| 522 | * @since 3.4.0 |
| 523 | * |
| 524 | * @param mixed $value Value of the setting. |
| 525 | * @param WP_Customize_Setting $this WP_Customize_Setting instance. |
| 526 | */ |
| 527 | do_action( 'customize_update_' . $this->type, $value, $this ); |
341 | | protected function _update_theme_mod( $value ) { |
342 | | // Handle non-array theme mod. |
343 | | if ( empty( $this->id_data[ 'keys' ] ) ) { |
344 | | set_theme_mod( $this->id_data[ 'base' ], $value ); |
345 | | return; |
346 | | } |
347 | | // Handle array-based theme mod. |
348 | | $mods = get_theme_mod( $this->id_data[ 'base' ] ); |
349 | | $mods = $this->multidimensional_replace( $mods, $this->id_data[ 'keys' ], $value ); |
350 | | if ( isset( $mods ) ) { |
351 | | set_theme_mod( $this->id_data[ 'base' ], $mods ); |
352 | | } |
| 535 | protected function _update_theme_mod() { |
| 536 | _deprecated_function( __METHOD__, '4.4.0' ); |
360 | | * @param mixed $value The value to update. |
361 | | * @return bool The result of saving the value. |
362 | | */ |
363 | | protected function _update_option( $value ) { |
364 | | // Handle non-array option. |
365 | | if ( empty( $this->id_data[ 'keys' ] ) ) |
366 | | return update_option( $this->id_data[ 'base' ], $value ); |
367 | | |
368 | | // Handle array-based options. |
369 | | $options = get_option( $this->id_data[ 'base' ] ); |
370 | | $options = $this->multidimensional_replace( $options, $this->id_data[ 'keys' ], $value ); |
371 | | if ( isset( $options ) ) |
372 | | return update_option( $this->id_data[ 'base' ], $options ); |
| 550 | * @see WP_Customize_Manager::save() |
| 551 | * @since 4.4.0 |
| 552 | * @access public |
| 553 | */ |
| 554 | final public function finalize_multidimensional_update() { |
| 555 | if ( ! $this->is_multidimensional_aggregated ) { |
| 556 | return; |
| 557 | } |
| 558 | $id_base = $this->id_data['base']; |
| 559 | if ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['update_finalized'] ) { |
| 560 | return; |
| 561 | } |
| 562 | if ( ! isset( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['updated_value'] ) ) { |
| 563 | return; |
| 564 | } |
| 565 | $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['updated_value']; |
| 566 | if ( isset( $root ) ) { |
| 567 | $this->set_root_value( $root ); |
| 568 | } |
| 569 | self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['update_finalized'] = true; |
393 | | /** |
394 | | * Filter a Customize setting value not handled as a theme_mod or option. |
395 | | * |
396 | | * The dynamic portion of the hook name, `$this->id_date['base']`, refers to |
397 | | * the base slug of the setting name. |
398 | | * |
399 | | * For settings handled as theme_mods or options, see those corresponding |
400 | | * functions for available hooks. |
401 | | * |
402 | | * @since 3.4.0 |
403 | | * |
404 | | * @param mixed $default The setting default value. Default empty. |
405 | | */ |
406 | | return apply_filters( 'customize_value_' . $this->id_data[ 'base' ], $this->default ); |
407 | | } |
408 | | |
409 | | // Handle non-array value |
410 | | if ( empty( $this->id_data[ 'keys' ] ) ) |
411 | | return $function( $this->id_data[ 'base' ], $this->default ); |
| 582 | if ( 'option' !== $this->type && 'theme_mod' !== $this->type ) { |
| 583 | if ( ! isset( $value ) ) { |
| 584 | $value = $this->default; |
| 585 | } |
413 | | // Handle array-based value |
414 | | $values = $function( $this->id_data[ 'base' ] ); |
415 | | return $this->multidimensional_get( $values, $this->id_data[ 'keys' ], $this->default ); |
| 587 | /** |
| 588 | * Filter a Customize setting value not handled as a theme_mod or option. |
| 589 | * |
| 590 | * The dynamic portion of the hook name, `$this->id_date['base']`, refers to |
| 591 | * the base slug of the setting name. |
| 592 | * |
| 593 | * For settings handled as theme_mods or options, see those corresponding |
| 594 | * functions for available hooks. |
| 595 | * |
| 596 | * @since 3.4.0 |
| 597 | * |
| 598 | * @param mixed $default The setting default value. Default empty. |
| 599 | */ |
| 600 | $value = apply_filters( 'customize_value_' . $this->id_data['base'], $value ); |
| 601 | } else { |
| 602 | // Handle multidimensional value |
| 603 | if ( ! empty( $this->id_data['keys'] ) ) { |
| 604 | $value = $this->multidimensional_get( $value, $this->id_data['keys'], $this->default ); |
| 605 | } |
| 606 | } |
| 607 | return $value; |