WordPress.org

Make WordPress Core

Ticket #32103: 32103.wip.diff

File 32103.wip.diff, 18.1 KB (added by westonruter, 5 years ago)

WIP

  • src/wp-includes/class-wp-customize-manager.php

    diff --git src/wp-includes/class-wp-customize-manager.php src/wp-includes/class-wp-customize-manager.php
    index 1505b60..e07bb8a 100644
    final class WP_Customize_Manager { 
    899899                do_action( 'customize_save', $this );
    900900
    901901                foreach ( $this->settings as $setting ) {
     902                        /*
     903                         * Note that aggregated multidimensional settings will only be
     904                         * prepared for saving with this call. They will actually be saved
     905                         * in the finalize_multidimensional_update() call below.
     906                         */
    902907                        $setting->save();
    903908                }
     909                foreach ( $this->settings as $setting ) {
     910                        $setting->finalize_multidimensional_update();
     911                }
    904912
    905913                /**
    906914                 * Fires after Customize settings have been saved.
  • src/wp-includes/class-wp-customize-setting.php

    diff --git src/wp-includes/class-wp-customize-setting.php src/wp-includes/class-wp-customize-setting.php
    index 98f37f9..0c8b36f 100644
    class WP_Customize_Setting { 
    8282        protected $id_data = array();
    8383
    8484        /**
     85         * Cache of multidimensional values to improve performance.
     86         *
     87         * @since 4.4.0
     88         * @access protected
     89         * @var array
     90         * @static
     91         */
     92        protected static $aggregated_multidimensionals = array();
     93
     94        /**
     95         * Whether the multidimensional setting is aggregated.
     96         *
     97         * @todo Do we need this? We can look at $aggregated_multidimensionals anyway
     98         * @since 4.4.0
     99         * @access protected
     100         * @var bool
     101         */
     102        protected $is_multidimensional_aggregated = false;
     103
     104        /**
    85105         * Constructor.
    86106         *
    87107         * Any supplied $args override class property defaults.
    class WP_Customize_Setting { 
    96116        public function __construct( $manager, $id, $args = array() ) {
    97117                $keys = array_keys( get_object_vars( $this ) );
    98118                foreach ( $keys as $key ) {
    99                         if ( isset( $args[ $key ] ) )
     119                        if ( isset( $args[ $key ] ) ) {
    100120                                $this->$key = $args[ $key ];
     121                        }
    101122                }
    102123
    103124                $this->manager = $manager;
    104125                $this->id = $id;
    105126
    106127                // Parse the ID for array keys.
    107                 $this->id_data[ 'keys' ] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
    108                 $this->id_data[ 'base' ] = array_shift( $this->id_data[ 'keys' ] );
     128                $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
     129                $this->id_data['base'] = array_shift( $this->id_data['keys'] );
    109130
    110131                // Rebuild the ID.
    111132                $this->id = $this->id_data[ 'base' ];
    112                 if ( ! empty( $this->id_data[ 'keys' ] ) )
    113                         $this->id .= '[' . implode( '][', $this->id_data[ 'keys' ] ) . ']';
     133                if ( ! empty( $this->id_data[ 'keys' ] ) ) {
     134                        $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
     135                }
    114136
    115                 if ( $this->sanitize_callback )
     137                if ( $this->sanitize_callback ) {
    116138                        add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
    117 
    118                 if ( $this->sanitize_js_callback )
     139                }
     140                if ( $this->sanitize_js_callback ) {
    119141                        add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
     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;
    120194        }
    121195
    122196        /**
    class WP_Customize_Setting { 
    165239                if ( ! isset( $this->_previewed_blog_id ) ) {
    166240                        $this->_previewed_blog_id = get_current_blog_id();
    167241                }
     242                $id_base = $this->id_data['base'];
     243                $is_multidimensional = ! empty( $this->id_data['keys'] );
     244                $multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
    168245
    169246                switch( $this->type ) {
    170247                        case 'theme_mod' :
    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                                }
    172259                                break;
    173260                        case 'option' :
    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;
    179272                                }
    180273                                break;
    181274                        default :
    class WP_Customize_Setting { 
    207300        }
    208301
    209302        /**
    210          * Callback function to filter the theme mods and options.
     303         * Callback function to filter non-multidimensional theme mods and options.
    211304         *
    212305         * If switch_to_blog() was called after the preview() method, and the current
    213306         * blog is now not the same blog, then this method does a no-op and returns
    214307         * the original value.
    215308         *
    216309         * @since 3.4.0
    217          * @uses WP_Customize_Setting::multidimensional_replace()
    218310         *
    219311         * @param mixed $original Old value.
    220312         * @return mixed New or old value.
    class WP_Customize_Setting { 
    231323                } else {
    232324                        $value = $post_value;
    233325                }
     326                return $value;
     327        }
    234328
    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'];
    236378        }
    237379
    238380        /**
    class WP_Customize_Setting { 
    299441        }
    300442
    301443        /**
     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        /**
    302493         * Save the value of the setting, using the related API.
    303494         *
    304495         * @since 3.4.0
    class WP_Customize_Setting { 
    307498         * @return mixed The result of saving the value.
    308499         */
    309500        protected function update( $value ) {
    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 );
    331528                }
    332529        }
    333530
    334531        /**
    335          * Update the theme mod from the value of the parameter.
    336          *
    337532         * @since 3.4.0
    338          *
    339          * @param mixed $value The value to update.
     533         * @deprecated 4.4.0 Deprecated in favor of update() method.
    340534         */
    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' );
    353537        }
    354538
    355539        /**
    356          * Update the option from the value of the setting.
    357          *
    358540         * @since 3.4.0
     541         * @deprecated 4.4.0 Deprecated in favor of update() method.
     542         */
     543        protected function _update_option() {
     544                _deprecated_function( __METHOD__, '4.4.0' );
     545        }
     546
     547        /**
     548         * Finalize/commit the updates for multidimensional settings.
    359549         *
    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;
    373570        }
    374571
    375572        /**
    class WP_Customize_Setting { 
    380577         * @return mixed The value.
    381578         */
    382579        public function value() {
    383                 // Get the callback that corresponds to the setting type.
    384                 switch( $this->type ) {
    385                         case 'theme_mod' :
    386                                 $function = 'get_theme_mod';
    387                                 break;
    388                         case 'option' :
    389                                 $function = 'get_option';
    390                                 break;
    391                         default :
     580                $value = $this->get_root_value();
    392581
    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                        }
    412586
    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;
    416608        }
    417609
    418610        /**
    class WP_Customize_Setting { 
    517709         * @param $root
    518710         * @param $keys
    519711         * @param mixed $value The value to update.
    520          * @return
     712         * @return mixed
    521713         */
    522714        final protected function multidimensional_replace( $root, $keys, $value ) {
    523715                if ( ! isset( $value ) )