Make WordPress Core


Ignore:
Timestamp:
10/24/2015 06:10:17 PM (9 years ago)
Author:
wonderboymusic
Message:

Customize: move WP_Customize_Setting subclasses to wp-includes/customize, they load in the exact same place.

See #34432.

Location:
trunk/src/wp-includes/customize
Files:
1 added
1 copied

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/customize/class-wp-customize-nav-menu-item-setting.php

    r35381 r35383  
    55 * @package WordPress
    66 * @subpackage Customize
    7  * @since 3.4.0
     7 * @since 4.4.0
    88 */
    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      * @access public
    22      * @var WP_Customize_Manager
    23      */
    24     public $manager;
    25 
    26     /**
    27      * Unique string identifier for the setting.
    28      *
    29      * @access public
    30      * @var string
    31      */
    32     public $id;
    33 
    34     /**
    35      * @access public
    36      * @var string
    37      */
    38     public $type = 'theme_mod';
    39 
    40     /**
    41      * Capability required to edit this setting.
    42      *
    43      * @var string
    44      */
    45     public $capability = 'edit_theme_options';
    46 
    47     /**
    48      * Feature a theme is required to support to enable this setting.
    49      *
    50      * @access public
    51      * @var string
    52      */
    53     public $theme_supports  = '';
    54     public $default         = '';
    55     public $transport       = 'refresh';
    56 
    57     /**
    58      * Server-side sanitization callback for the setting's value.
    59      *
    60      * @var callback
    61      */
    62     public $sanitize_callback    = '';
    63     public $sanitize_js_callback = '';
    64 
    65     /**
    66      * Whether or not the setting is initially dirty when created.
    67      *
    68      * This is used to ensure that a setting will be sent from the pane to the
    69      * preview when loading the Customizer. Normally a setting only is synced to
    70      * the preview if it has been changed. This allows the setting to be sent
    71      * from the start.
    72      *
    73      * @since 4.2.0
    74      * @access public
    75      * @var bool
    76      */
    77     public $dirty = false;
    78 
    79     /**
    80      * @var array
    81      */
    82     protected $id_data = array();
    83 
    84     /**
    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      * @since 4.4.0
    98      * @access protected
    99      * @var bool
    100      */
    101     protected $is_multidimensional_aggregated = false;
    102 
    103     /**
    104      * Constructor.
    105      *
    106      * Any supplied $args override class property defaults.
    107      *
    108      * @since 3.4.0
    109      *
    110      * @param WP_Customize_Manager $manager
    111      * @param string               $id      An specific ID of the setting. Can be a
    112      *                                      theme mod or option name.
    113      * @param array                $args    Setting arguments.
    114      */
    115     public function __construct( $manager, $id, $args = array() ) {
    116         $keys = array_keys( get_object_vars( $this ) );
    117         foreach ( $keys as $key ) {
    118             if ( isset( $args[ $key ] ) ) {
    119                 $this->$key = $args[ $key ];
    120             }
    121         }
    122 
    123         $this->manager = $manager;
    124         $this->id = $id;
    125 
    126         // Parse the ID for array keys.
    127         $this->id_data['keys'] = preg_split( '/\[/', str_replace( ']', '', $this->id ) );
    128         $this->id_data['base'] = array_shift( $this->id_data['keys'] );
    129 
    130         // Rebuild the ID.
    131         $this->id = $this->id_data[ 'base' ];
    132         if ( ! empty( $this->id_data[ 'keys' ] ) ) {
    133             $this->id .= '[' . implode( '][', $this->id_data['keys'] ) . ']';
    134         }
    135 
    136         if ( $this->sanitize_callback ) {
    137             add_filter( "customize_sanitize_{$this->id}", $this->sanitize_callback, 10, 2 );
    138         }
    139         if ( $this->sanitize_js_callback ) {
    140             add_filter( "customize_sanitize_js_{$this->id}", $this->sanitize_js_callback, 10, 2 );
    141         }
    142 
    143         if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
    144             // Other setting types can opt-in to aggregate multidimensional explicitly.
    145             $this->aggregate_multidimensional();
    146 
    147             // Allow option settings to indicate whether they should be autoloaded.
    148             if ( 'option' === $this->type && isset( $args['autoload'] ) ) {
    149                 self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] = $args['autoload'];
    150             }
    151         }
    152     }
    153 
    154     /**
    155      * Get parsed ID data for multidimensional setting.
    156      *
    157      * @since 4.4.0
    158      * @access public
    159      *
    160      * @return array {
    161      *     ID data for multidimensional setting.
    162      *
    163      *     @type string $base ID base
    164      *     @type array  $keys Keys for multidimensional array.
    165      * }
    166      */
    167     final public function id_data() {
    168         return $this->id_data;
    169     }
    170 
    171     /**
    172      * Set up the setting for aggregated multidimensional values.
    173      *
    174      * When a multidimensional setting gets aggregated, all of its preview and update
    175      * calls get combined into one call, greatly improving performance.
    176      *
    177      * @since 4.4.0
    178      * @access protected
    179      */
    180     protected function aggregate_multidimensional() {
    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                 'root_value'                => $this->get_root_value( array() ), // Root value for initial state, manipulated by preview and update calls.
    190             );
    191         }
    192 
    193         if ( ! empty( $this->id_data['keys'] ) ) {
    194             $this->is_multidimensional_aggregated = true;
    195         }
    196     }
    197 
    198     /**
    199      * The ID for the current blog when the preview() method was called.
    200      *
    201      * @since 4.2.0
    202      * @access protected
    203      * @var int
    204      */
    205     protected $_previewed_blog_id;
    206 
    207     /**
    208      * Return true if the current blog is not the same as the previewed blog.
    209      *
    210      * @since 4.2.0
    211      * @access public
    212      *
    213      * @return bool If preview() has been called.
    214      */
    215     public function is_current_blog_previewed() {
    216         if ( ! isset( $this->_previewed_blog_id ) ) {
    217             return false;
    218         }
    219         return ( get_current_blog_id() === $this->_previewed_blog_id );
    220     }
    221 
    222     /**
    223      * Original non-previewed value stored by the preview method.
    224      *
    225      * @see WP_Customize_Setting::preview()
    226      * @since 4.1.1
    227      * @var mixed
    228      */
    229     protected $_original_value;
    230 
    231     /**
    232      * Add filters to supply the setting's value when accessed.
    233      *
    234      * If the setting already has a pre-existing value and there is no incoming
    235      * post value for the setting, then this method will short-circuit since
    236      * there is no change to preview.
    237      *
    238      * @since 3.4.0
    239      * @since 4.4.0 Added boolean return value.
    240      * @access public
    241      *
    242      * @return bool False when preview short-circuits due no change needing to be previewed.
    243      */
    244     public function preview() {
    245         if ( ! isset( $this->_previewed_blog_id ) ) {
    246             $this->_previewed_blog_id = get_current_blog_id();
    247         }
    248         $id_base = $this->id_data['base'];
    249         $is_multidimensional = ! empty( $this->id_data['keys'] );
    250         $multidimensional_filter = array( $this, '_multidimensional_preview_filter' );
    251 
    252         /*
    253          * Check if the setting has a pre-existing value (an isset check),
    254          * and if doesn't have any incoming post value. If both checks are true,
    255          * then the preview short-circuits because there is nothing that needs
    256          * to be previewed.
    257          */
    258         $undefined = new stdClass();
    259         $needs_preview = ( $undefined !== $this->post_value( $undefined ) );
    260         $value = null;
    261 
    262         // Since no post value was defined, check if we have an initial value set.
    263         if ( ! $needs_preview ) {
    264             if ( $this->is_multidimensional_aggregated ) {
    265                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
    266                 $value = $this->multidimensional_get( $root, $this->id_data['keys'], $undefined );
    267             } else {
    268                 $default = $this->default;
    269                 $this->default = $undefined; // Temporarily set default to undefined so we can detect if existing value is set.
    270                 $value = $this->value();
    271                 $this->default = $default;
    272             }
    273             $needs_preview = ( $undefined === $value ); // Because the default needs to be supplied.
    274         }
    275 
    276         if ( ! $needs_preview ) {
    277             return false;
    278         }
    279 
    280         switch ( $this->type ) {
    281             case 'theme_mod' :
    282                 if ( ! $is_multidimensional ) {
    283                     add_filter( "theme_mod_{$id_base}", array( $this, '_preview_filter' ) );
    284                 } else {
    285                     if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
    286                         // Only add this filter once for this ID base.
    287                         add_filter( "theme_mod_{$id_base}", $multidimensional_filter );
    288                     }
    289                     self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
    290                 }
    291                 break;
    292             case 'option' :
    293                 if ( ! $is_multidimensional ) {
    294                     add_filter( "pre_option_{$id_base}", array( $this, '_preview_filter' ) );
    295                 } else {
    296                     if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
    297                         // Only add these filters once for this ID base.
    298                         add_filter( "option_{$id_base}", $multidimensional_filter );
    299                         add_filter( "default_option_{$id_base}", $multidimensional_filter );
    300                     }
    301                     self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'][ $this->id ] = $this;
    302                 }
    303                 break;
    304             default :
    305 
    306                 /**
    307                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
    308                  * not handled as theme_mods or options.
    309                  *
    310                  * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
    311                  *
    312                  * @since 3.4.0
    313                  *
    314                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
    315                  */
    316                 do_action( "customize_preview_{$this->id}", $this );
    317 
    318                 /**
    319                  * Fires when the {@see WP_Customize_Setting::preview()} method is called for settings
    320                  * not handled as theme_mods or options.
    321                  *
    322                  * The dynamic portion of the hook name, `$this->type`, refers to the setting type.
    323                  *
    324                  * @since 4.1.0
    325                  *
    326                  * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
    327                  */
    328                 do_action( "customize_preview_{$this->type}", $this );
    329         }
    330         return true;
    331     }
    332 
    333     /**
    334      * Callback function to filter non-multidimensional theme mods and options.
    335      *
    336      * If switch_to_blog() was called after the preview() method, and the current
    337      * blog is now not the same blog, then this method does a no-op and returns
    338      * the original value.
    339      *
    340      * @since 3.4.0
    341      *
    342      * @param mixed $original Old value.
    343      * @return mixed New or old value.
    344      */
    345     public function _preview_filter( $original ) {
    346         if ( ! $this->is_current_blog_previewed() ) {
    347             return $original;
    348         }
    349 
    350         $undefined = new stdClass(); // Symbol hack.
    351         $post_value = $this->post_value( $undefined );
    352         if ( $undefined !== $post_value ) {
    353             $value = $post_value;
    354         } else {
    355             /*
    356              * Note that we don't use $original here because preview() will
    357              * not add the filter in the first place if it has an initial value
    358              * and there is no post value.
    359              */
    360             $value = $this->default;
    361         }
    362         return $value;
    363     }
    364 
    365     /**
    366      * Callback function to filter multidimensional theme mods and options.
    367      *
    368      * For all multidimensional settings of a given type, the preview filter for
    369      * the first setting previewed will be used to apply the values for the others.
    370      *
    371      * @since 4.4.0
    372      * @access public
    373      *
    374      * @see WP_Customize_Setting::$aggregated_multidimensionals
    375      * @param mixed $original Original root value.
    376      * @return mixed New or old value.
    377      */
    378     public function _multidimensional_preview_filter( $original ) {
    379         if ( ! $this->is_current_blog_previewed() ) {
    380             return $original;
    381         }
    382 
    383         $id_base = $this->id_data['base'];
    384 
    385         // If no settings have been previewed yet (which should not be the case, since $this is), just pass through the original value.
    386         if ( empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] ) ) {
    387             return $original;
    388         }
    389 
    390         foreach ( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['previewed_instances'] as $previewed_setting ) {
    391             // Skip applying previewed value for any settings that have already been applied.
    392             if ( ! empty( self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] ) ) {
    393                 continue;
    394             }
    395 
    396             // Do the replacements of the posted/default sub value into the root value.
    397             $value = $previewed_setting->post_value( $previewed_setting->default );
    398             $root = self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'];
    399             $root = $previewed_setting->multidimensional_replace( $root, $previewed_setting->id_data['keys'], $value );
    400             self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['root_value'] = $root;
    401 
    402             // Mark this setting having been applied so that it will be skipped when the filter is called again.
    403             self::$aggregated_multidimensionals[ $previewed_setting->type ][ $id_base ]['preview_applied_instances'][ $previewed_setting->id ] = true;
    404         }
    405 
    406         return self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
    407     }
    408 
    409     /**
    410      * Check user capabilities and theme supports, and then save
    411      * the value of the setting.
    412      *
    413      * @since 3.4.0
    414      *
    415      * @return false|void False if cap check fails or value isn't set.
    416      */
    417     final public function save() {
    418         $value = $this->post_value();
    419 
    420         if ( ! $this->check_capabilities() || ! isset( $value ) )
    421             return false;
    422 
    423         /**
    424          * Fires when the WP_Customize_Setting::save() method is called.
    425          *
    426          * The dynamic portion of the hook name, `$this->id_data['base']` refers to
    427          * the base slug of the setting name.
    428          *
    429          * @since 3.4.0
    430          *
    431          * @param WP_Customize_Setting $this {@see WP_Customize_Setting} instance.
    432          */
    433         do_action( 'customize_save_' . $this->id_data[ 'base' ], $this );
    434 
    435         $this->update( $value );
    436     }
    437 
    438     /**
    439      * Fetch and sanitize the $_POST value for the setting.
    440      *
    441      * @since 3.4.0
    442      *
    443      * @param mixed $default A default value which is used as a fallback. Default is null.
    444      * @return mixed The default value on failure, otherwise the sanitized value.
    445      */
    446     final public function post_value( $default = null ) {
    447         return $this->manager->post_value( $this, $default );
    448     }
    449 
    450     /**
    451      * Sanitize an input.
    452      *
    453      * @since 3.4.0
    454      *
    455      * @param string|array $value The value to sanitize.
    456      * @return string|array|null Null if an input isn't valid, otherwise the sanitized value.
    457      */
    458     public function sanitize( $value ) {
    459         $value = wp_unslash( $value );
    460 
    461         /**
    462          * Filter a Customize setting value in un-slashed form.
    463          *
    464          * @since 3.4.0
    465          *
    466          * @param mixed                $value Value of the setting.
    467          * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
    468          */
    469         return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
    470     }
    471 
    472     /**
    473      * Get the root value for a setting, especially for multidimensional ones.
    474      *
    475      * @since 4.4.0
    476      * @access protected
    477      *
    478      * @param mixed $default Value to return if root does not exist.
    479      * @return mixed
    480      */
    481     protected function get_root_value( $default = null ) {
    482         $id_base = $this->id_data['base'];
    483         if ( 'option' === $this->type ) {
    484             return get_option( $id_base, $default );
    485         } else if ( 'theme_mod' ) {
    486             return get_theme_mod( $id_base, $default );
    487         } else {
    488             /*
    489              * Any WP_Customize_Setting subclass implementing aggregate multidimensional
    490              * will need to override this method to obtain the data from the appropriate
    491              * location.
    492              */
    493             return $default;
    494         }
    495     }
    496 
    497     /**
    498      * Set the root value for a setting, especially for multidimensional ones.
    499      *
    500      * @since 4.4.0
    501      * @access protected
    502      *
    503      * @param mixed $value Value to set as root of multidimensional setting.
    504      * @return bool Whether the multidimensional root was updated successfully.
    505      */
    506     protected function set_root_value( $value ) {
    507         $id_base = $this->id_data['base'];
    508         if ( 'option' === $this->type ) {
    509             $autoload = true;
    510             if ( isset( self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'] ) ) {
    511                 $autoload = self::$aggregated_multidimensionals[ $this->type ][ $this->id_data['base'] ]['autoload'];
    512             }
    513             return update_option( $id_base, $value, $autoload );
    514         } else if ( 'theme_mod' ) {
    515             set_theme_mod( $id_base, $value );
    516             return true;
    517         } else {
    518             /*
    519              * Any WP_Customize_Setting subclass implementing aggregate multidimensional
    520              * will need to override this method to obtain the data from the appropriate
    521              * location.
    522              */
    523             return false;
    524         }
    525     }
    526 
    527     /**
    528      * Save the value of the setting, using the related API.
    529      *
    530      * @since 3.4.0
    531      *
    532      * @param mixed $value The value to update.
    533      * @return bool The result of saving the value.
    534      */
    535     protected function update( $value ) {
    536         $id_base = $this->id_data['base'];
    537         if ( 'option' === $this->type || 'theme_mod' === $this->type ) {
    538             if ( ! $this->is_multidimensional_aggregated ) {
    539                 return $this->set_root_value( $value );
    540             } else {
    541                 $root = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
    542                 $root = $this->multidimensional_replace( $root, $this->id_data['keys'], $value );
    543                 self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'] = $root;
    544                 return $this->set_root_value( $root );
    545             }
    546         } else {
    547             /**
    548              * Fires when the {@see WP_Customize_Setting::update()} method is called for settings
    549              * not handled as theme_mods or options.
    550              *
    551              * The dynamic portion of the hook name, `$this->type`, refers to the type of setting.
    552              *
    553              * @since 3.4.0
    554              *
    555              * @param mixed                $value Value of the setting.
    556              * @param WP_Customize_Setting $this  WP_Customize_Setting instance.
    557              */
    558             do_action( "customize_update_{$this->type}", $value, $this );
    559 
    560             return has_action( "customize_update_{$this->type}" );
    561         }
    562     }
    563 
    564     /**
    565      * Deprecated method.
    566      *
    567      * @since 3.4.0
    568      * @deprecated 4.4.0 Deprecated in favor of update() method.
    569      */
    570     protected function _update_theme_mod() {
    571         _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
    572     }
    573 
    574     /**
    575      * Deprecated method.
    576      *
    577      * @since 3.4.0
    578      * @deprecated 4.4.0 Deprecated in favor of update() method.
    579      */
    580     protected function _update_option() {
    581         _deprecated_function( __METHOD__, '4.4.0', __CLASS__ . '::update()' );
    582     }
    583 
    584     /**
    585      * Fetch the value of the setting.
    586      *
    587      * @since 3.4.0
    588      *
    589      * @return mixed The value.
    590      */
    591     public function value() {
    592         $id_base = $this->id_data['base'];
    593         $is_core_type = ( 'option' === $this->type || 'theme_mod' === $this->type );
    594 
    595         if ( ! $is_core_type && ! $this->is_multidimensional_aggregated ) {
    596             $value = $this->get_root_value( $this->default );
    597 
    598             /**
    599              * Filter a Customize setting value not handled as a theme_mod or option.
    600              *
    601              * The dynamic portion of the hook name, `$this->id_date['base']`, refers to
    602              * the base slug of the setting name.
    603              *
    604              * For settings handled as theme_mods or options, see those corresponding
    605              * functions for available hooks.
    606              *
    607              * @since 3.4.0
    608              *
    609              * @param mixed $default The setting default value. Default empty.
    610              */
    611             $value = apply_filters( "customize_value_{$id_base}", $value );
    612         } else if ( $this->is_multidimensional_aggregated ) {
    613             $root_value = self::$aggregated_multidimensionals[ $this->type ][ $id_base ]['root_value'];
    614             $value = $this->multidimensional_get( $root_value, $this->id_data['keys'], $this->default );
    615         } else {
    616             $value = $this->get_root_value( $this->default );
    617         }
    618         return $value;
    619     }
    620 
    621     /**
    622      * Sanitize the setting's value for use in JavaScript.
    623      *
    624      * @since 3.4.0
    625      *
    626      * @return mixed The requested escaped value.
    627      */
    628     public function js_value() {
    629 
    630         /**
    631          * Filter a Customize setting value for use in JavaScript.
    632          *
    633          * The dynamic portion of the hook name, `$this->id`, refers to the setting ID.
    634          *
    635          * @since 3.4.0
    636          *
    637          * @param mixed                $value The setting value.
    638          * @param WP_Customize_Setting $this  {@see WP_Customize_Setting} instance.
    639          */
    640         $value = apply_filters( "customize_sanitize_js_{$this->id}", $this->value(), $this );
    641 
    642         if ( is_string( $value ) )
    643             return html_entity_decode( $value, ENT_QUOTES, 'UTF-8');
    644 
    645         return $value;
    646     }
    647 
    648     /**
    649      * Validate user capabilities whether the theme supports the setting.
    650      *
    651      * @since 3.4.0
    652      *
    653      * @return bool False if theme doesn't support the setting or user can't change setting, otherwise true.
    654      */
    655     final public function check_capabilities() {
    656         if ( $this->capability && ! call_user_func_array( 'current_user_can', (array) $this->capability ) )
    657             return false;
    658 
    659         if ( $this->theme_supports && ! call_user_func_array( 'current_theme_supports', (array) $this->theme_supports ) )
    660             return false;
    661 
    662         return true;
    663     }
    664 
    665     /**
    666      * Multidimensional helper function.
    667      *
    668      * @since 3.4.0
    669      *
    670      * @param $root
    671      * @param $keys
    672      * @param bool $create Default is false.
    673      * @return array|void Keys are 'root', 'node', and 'key'.
    674      */
    675     final protected function multidimensional( &$root, $keys, $create = false ) {
    676         if ( $create && empty( $root ) )
    677             $root = array();
    678 
    679         if ( ! isset( $root ) || empty( $keys ) )
    680             return;
    681 
    682         $last = array_pop( $keys );
    683         $node = &$root;
    684 
    685         foreach ( $keys as $key ) {
    686             if ( $create && ! isset( $node[ $key ] ) )
    687                 $node[ $key ] = array();
    688 
    689             if ( ! is_array( $node ) || ! isset( $node[ $key ] ) )
    690                 return;
    691 
    692             $node = &$node[ $key ];
    693         }
    694 
    695         if ( $create ) {
    696             if ( ! is_array( $node ) ) {
    697                 // account for an array overriding a string or object value
    698                 $node = array();
    699             }
    700             if ( ! isset( $node[ $last ] ) ) {
    701                 $node[ $last ] = array();
    702             }
    703         }
    704 
    705         if ( ! isset( $node[ $last ] ) )
    706             return;
    707 
    708         return array(
    709             'root' => &$root,
    710             'node' => &$node,
    711             'key'  => $last,
    712         );
    713     }
    714 
    715     /**
    716      * Will attempt to replace a specific value in a multidimensional array.
    717      *
    718      * @since 3.4.0
    719      *
    720      * @param $root
    721      * @param $keys
    722      * @param mixed $value The value to update.
    723      * @return mixed
    724      */
    725     final protected function multidimensional_replace( $root, $keys, $value ) {
    726         if ( ! isset( $value ) )
    727             return $root;
    728         elseif ( empty( $keys ) ) // If there are no keys, we're replacing the root.
    729             return $value;
    730 
    731         $result = $this->multidimensional( $root, $keys, true );
    732 
    733         if ( isset( $result ) )
    734             $result['node'][ $result['key'] ] = $value;
    735 
    736         return $root;
    737     }
    738 
    739     /**
    740      * Will attempt to fetch a specific value from a multidimensional array.
    741      *
    742      * @since 3.4.0
    743      *
    744      * @param $root
    745      * @param $keys
    746      * @param mixed $default A default value which is used as a fallback. Default is null.
    747      * @return mixed The requested value or the default value.
    748      */
    749     final protected function multidimensional_get( $root, $keys, $default = null ) {
    750         if ( empty( $keys ) ) // If there are no keys, test the root.
    751             return isset( $root ) ? $root : $default;
    752 
    753         $result = $this->multidimensional( $root, $keys );
    754         return isset( $result ) ? $result['node'][ $result['key'] ] : $default;
    755     }
    756 
    757     /**
    758      * Will attempt to check if a specific value in a multidimensional array is set.
    759      *
    760      * @since 3.4.0
    761      *
    762      * @param $root
    763      * @param $keys
    764      * @return bool True if value is set, false if not.
    765      */
    766     final protected function multidimensional_isset( $root, $keys ) {
    767         $result = $this->multidimensional_get( $root, $keys );
    768         return isset( $result );
    769     }
    770 }
    771 
    772 /**
    773  * A setting that is used to filter a value, but will not save the results.
    774  *
    775  * Results should be properly handled using another setting or callback.
    776  *
    777  * @since 3.4.0
    778  *
    779  * @see WP_Customize_Setting
    780  */
    781 class WP_Customize_Filter_Setting extends WP_Customize_Setting {
    782 
    783     /**
    784      * @since 3.4.0
    785      */
    786     public function update( $value ) {}
    787 }
    788 
    789 /**
    790  * A setting that is used to filter a value, but will not save the results.
    791  *
    792  * Results should be properly handled using another setting or callback.
    793  *
    794  * @since 3.4.0
    795  *
    796  * @see WP_Customize_Setting
    797  */
    798 final class WP_Customize_Header_Image_Setting extends WP_Customize_Setting {
    799     public $id = 'header_image_data';
    800 
    801     /**
    802      * @since 3.4.0
    803      *
    804      * @global Custom_Image_Header $custom_image_header
    805      *
    806      * @param $value
    807      */
    808     public function update( $value ) {
    809         global $custom_image_header;
    810 
    811         // If the value doesn't exist (removed or random),
    812         // use the header_image value.
    813         if ( ! $value )
    814             $value = $this->manager->get_setting('header_image')->post_value();
    815 
    816         if ( is_array( $value ) && isset( $value['choice'] ) )
    817             $custom_image_header->set_header_image( $value['choice'] );
    818         else
    819             $custom_image_header->set_header_image( $value );
    820     }
    821 }
    822 
    823 /**
    824  * Customizer Background Image Setting class.
    825  *
    826  * @since 3.4.0
    827  *
    828  * @see WP_Customize_Setting
    829  */
    830 final class WP_Customize_Background_Image_Setting extends WP_Customize_Setting {
    831     public $id = 'background_image_thumb';
    832 
    833     /**
    834      * @since 3.4.0
    835      *
    836      * @param $value
    837      */
    838     public function update( $value ) {
    839         remove_theme_mod( 'background_image_thumb' );
    840     }
    841 }
    8429
    84310/**
     
    1646813    }
    1647814}
    1648 
    1649 /**
    1650  * Customize Setting to represent a nav_menu.
    1651  *
    1652  * Subclass of WP_Customize_Setting to represent a nav_menu taxonomy term, and
    1653  * the IDs for the nav_menu_items associated with the nav menu.
    1654  *
    1655  * @since 4.3.0
    1656  *
    1657  * @see wp_get_nav_menu_object()
    1658  * @see WP_Customize_Setting
    1659  */
    1660 class WP_Customize_Nav_Menu_Setting extends WP_Customize_Setting {
    1661 
    1662     const ID_PATTERN = '/^nav_menu\[(?P<id>-?\d+)\]$/';
    1663 
    1664     const TAXONOMY = 'nav_menu';
    1665 
    1666     const TYPE = 'nav_menu';
    1667 
    1668     /**
    1669      * Setting type.
    1670      *
    1671      * @since 4.3.0
    1672      * @access public
    1673      * @var string
    1674      */
    1675     public $type = self::TYPE;
    1676 
    1677     /**
    1678      * Default setting value.
    1679      *
    1680      * @since 4.3.0
    1681      * @access public
    1682      * @var array
    1683      *
    1684      * @see wp_get_nav_menu_object()
    1685      */
    1686     public $default = array(
    1687         'name'        => '',
    1688         'description' => '',
    1689         'parent'      => 0,
    1690         'auto_add'    => false,
    1691     );
    1692 
    1693     /**
    1694      * Default transport.
    1695      *
    1696      * @since 4.3.0
    1697      * @access public
    1698      * @var string
    1699      */
    1700     public $transport = 'postMessage';
    1701 
    1702     /**
    1703      * The term ID represented by this setting instance.
    1704      *
    1705      * A negative value represents a placeholder ID for a new menu not yet saved.
    1706      *
    1707      * @since 4.3.0
    1708      * @access public
    1709      * @var int
    1710      */
    1711     public $term_id;
    1712 
    1713     /**
    1714      * Previous (placeholder) term ID used before creating a new menu.
    1715      *
    1716      * This value will be exported to JS via the customize_save_response filter
    1717      * so that JavaScript can update the settings to refer to the newly-assigned
    1718      * term ID. This value is always negative to indicate it does not refer to
    1719      * a real term.
    1720      *
    1721      * @since 4.3.0
    1722      * @access public
    1723      * @var int
    1724      *
    1725      * @see WP_Customize_Nav_Menu_Setting::update()
    1726      * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
    1727      */
    1728     public $previous_term_id;
    1729 
    1730     /**
    1731      * Whether or not preview() was called.
    1732      *
    1733      * @since 4.3.0
    1734      * @access protected
    1735      * @var bool
    1736      */
    1737     protected $is_previewed = false;
    1738 
    1739     /**
    1740      * Whether or not update() was called.
    1741      *
    1742      * @since 4.3.0
    1743      * @access protected
    1744      * @var bool
    1745      */
    1746     protected $is_updated = false;
    1747 
    1748     /**
    1749      * Status for calling the update method, used in customize_save_response filter.
    1750      *
    1751      * When status is inserted, the placeholder term ID is stored in $previous_term_id.
    1752      * When status is error, the error is stored in $update_error.
    1753      *
    1754      * @since 4.3.0
    1755      * @access public
    1756      * @var string updated|inserted|deleted|error
    1757      *
    1758      * @see WP_Customize_Nav_Menu_Setting::update()
    1759      * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
    1760      */
    1761     public $update_status;
    1762 
    1763     /**
    1764      * Any error object returned by wp_update_nav_menu_object() when setting is updated.
    1765      *
    1766      * @since 4.3.0
    1767      * @access public
    1768      * @var WP_Error
    1769      *
    1770      * @see WP_Customize_Nav_Menu_Setting::update()
    1771      * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
    1772      */
    1773     public $update_error;
    1774 
    1775     /**
    1776      * Constructor.
    1777      *
    1778      * Any supplied $args override class property defaults.
    1779      *
    1780      * @since 4.3.0
    1781      * @access public
    1782      *
    1783      * @param WP_Customize_Manager $manager Bootstrap Customizer instance.
    1784      * @param string               $id      An specific ID of the setting. Can be a
    1785      *                                      theme mod or option name.
    1786      * @param array                $args    Optional. Setting arguments.
    1787      *
    1788      * @throws Exception If $id is not valid for this setting type.
    1789      */
    1790     public function __construct( WP_Customize_Manager $manager, $id, array $args = array() ) {
    1791         if ( empty( $manager->nav_menus ) ) {
    1792             throw new Exception( 'Expected WP_Customize_Manager::$nav_menus to be set.' );
    1793         }
    1794 
    1795         if ( ! preg_match( self::ID_PATTERN, $id, $matches ) ) {
    1796             throw new Exception( "Illegal widget setting ID: $id" );
    1797         }
    1798 
    1799         $this->term_id = intval( $matches['id'] );
    1800 
    1801         parent::__construct( $manager, $id, $args );
    1802     }
    1803 
    1804     /**
    1805      * Get the instance data for a given widget setting.
    1806      *
    1807      * @since 4.3.0
    1808      * @access public
    1809      *
    1810      * @see wp_get_nav_menu_object()
    1811      *
    1812      * @return array Instance data.
    1813      */
    1814     public function value() {
    1815         if ( $this->is_previewed && $this->_previewed_blog_id === get_current_blog_id() ) {
    1816             $undefined  = new stdClass(); // Symbol.
    1817             $post_value = $this->post_value( $undefined );
    1818 
    1819             if ( $undefined === $post_value ) {
    1820                 $value = $this->_original_value;
    1821             } else {
    1822                 $value = $post_value;
    1823             }
    1824         } else {
    1825             $value = false;
    1826 
    1827             // Note that a term_id of less than one indicates a nav_menu not yet inserted.
    1828             if ( $this->term_id > 0 ) {
    1829                 $term = wp_get_nav_menu_object( $this->term_id );
    1830 
    1831                 if ( $term ) {
    1832                     $value = wp_array_slice_assoc( (array) $term, array_keys( $this->default ) );
    1833 
    1834                     $nav_menu_options  = (array) get_option( 'nav_menu_options', array() );
    1835                     $value['auto_add'] = false;
    1836 
    1837                     if ( isset( $nav_menu_options['auto_add'] ) && is_array( $nav_menu_options['auto_add'] ) ) {
    1838                         $value['auto_add'] = in_array( $term->term_id, $nav_menu_options['auto_add'] );
    1839                     }
    1840                 }
    1841             }
    1842 
    1843             if ( ! is_array( $value ) ) {
    1844                 $value = $this->default;
    1845             }
    1846         }
    1847         return $value;
    1848     }
    1849 
    1850     /**
    1851      * Handle previewing the setting.
    1852      *
    1853      * @since 4.3.0
    1854      * @since 4.4.0 Added boolean return value
    1855      * @access public
    1856      *
    1857      * @see WP_Customize_Manager::post_value()
    1858      *
    1859      * @return bool False if method short-circuited due to no-op.
    1860      */
    1861     public function preview() {
    1862         if ( $this->is_previewed ) {
    1863             return false;
    1864         }
    1865 
    1866         $undefined = new stdClass();
    1867         $is_placeholder = ( $this->term_id < 0 );
    1868         $is_dirty = ( $undefined !== $this->post_value( $undefined ) );
    1869         if ( ! $is_placeholder && ! $is_dirty ) {
    1870             return false;
    1871         }
    1872 
    1873         $this->is_previewed       = true;
    1874         $this->_original_value    = $this->value();
    1875         $this->_previewed_blog_id = get_current_blog_id();
    1876 
    1877         add_filter( 'wp_get_nav_menus', array( $this, 'filter_wp_get_nav_menus' ), 10, 2 );
    1878         add_filter( 'wp_get_nav_menu_object', array( $this, 'filter_wp_get_nav_menu_object' ), 10, 2 );
    1879         add_filter( 'default_option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
    1880         add_filter( 'option_nav_menu_options', array( $this, 'filter_nav_menu_options' ) );
    1881 
    1882         return true;
    1883     }
    1884 
    1885     /**
    1886      * Filter the wp_get_nav_menus() result to ensure the inserted menu object is included, and the deleted one is removed.
    1887      *
    1888      * @since 4.3.0
    1889      * @access public
    1890      *
    1891      * @see wp_get_nav_menus()
    1892      *
    1893      * @param array $menus An array of menu objects.
    1894      * @param array $args  An array of arguments used to retrieve menu objects.
    1895      * @return array
    1896      */
    1897     public function filter_wp_get_nav_menus( $menus, $args ) {
    1898         if ( get_current_blog_id() !== $this->_previewed_blog_id ) {
    1899             return $menus;
    1900         }
    1901 
    1902         $setting_value = $this->value();
    1903         $is_delete = ( false === $setting_value );
    1904         $index = -1;
    1905 
    1906         // Find the existing menu item's position in the list.
    1907         foreach ( $menus as $i => $menu ) {
    1908             if ( (int) $this->term_id === (int) $menu->term_id || (int) $this->previous_term_id === (int) $menu->term_id ) {
    1909                 $index = $i;
    1910                 break;
    1911             }
    1912         }
    1913 
    1914         if ( $is_delete ) {
    1915             // Handle deleted menu by removing it from the list.
    1916             if ( -1 !== $index ) {
    1917                 array_splice( $menus, $index, 1 );
    1918             }
    1919         } else {
    1920             // Handle menus being updated or inserted.
    1921             $menu_obj = (object) array_merge( array(
    1922                 'term_id'          => $this->term_id,
    1923                 'term_taxonomy_id' => $this->term_id,
    1924                 'slug'             => sanitize_title( $setting_value['name'] ),
    1925                 'count'            => 0,
    1926                 'term_group'       => 0,
    1927                 'taxonomy'         => self::TAXONOMY,
    1928                 'filter'           => 'raw',
    1929             ), $setting_value );
    1930 
    1931             array_splice( $menus, $index, ( -1 === $index ? 0 : 1 ), array( $menu_obj ) );
    1932         }
    1933 
    1934         // Make sure the menu objects get re-sorted after an update/insert.
    1935         if ( ! $is_delete && ! empty( $args['orderby'] ) ) {
    1936             $this->_current_menus_sort_orderby = $args['orderby'];
    1937             usort( $menus, array( $this, '_sort_menus_by_orderby' ) );
    1938         }
    1939         // @todo add support for $args['hide_empty'] === true
    1940 
    1941         return $menus;
    1942     }
    1943 
    1944     /**
    1945      * Temporary non-closure passing of orderby value to function.
    1946      *
    1947      * @since 4.3.0
    1948      * @access protected
    1949      * @var string
    1950      *
    1951      * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
    1952      * @see WP_Customize_Nav_Menu_Setting::_sort_menus_by_orderby()
    1953      */
    1954     protected $_current_menus_sort_orderby;
    1955 
    1956     /**
    1957      * Sort menu objects by the class-supplied orderby property.
    1958      *
    1959      * This is a workaround for a lack of closures.
    1960      *
    1961      * @since 4.3.0
    1962      * @access protected
    1963      * @param object $menu1
    1964      * @param object $menu2
    1965      * @return int
    1966      *
    1967      * @see WP_Customize_Nav_Menu_Setting::filter_wp_get_nav_menus()
    1968      */
    1969     protected function _sort_menus_by_orderby( $menu1, $menu2 ) {
    1970         $key = $this->_current_menus_sort_orderby;
    1971         return strcmp( $menu1->$key, $menu2->$key );
    1972     }
    1973 
    1974     /**
    1975      * Filter the wp_get_nav_menu_object() result to supply the previewed menu object.
    1976      *
    1977      * Requesting a nav_menu object by anything but ID is not supported.
    1978      *
    1979      * @since 4.3.0
    1980      * @access public
    1981      *
    1982      * @see wp_get_nav_menu_object()
    1983      *
    1984      * @param object|null $menu_obj Object returned by wp_get_nav_menu_object().
    1985      * @param string      $menu_id  ID of the nav_menu term. Requests by slug or name will be ignored.
    1986      * @return object|null
    1987      */
    1988     public function filter_wp_get_nav_menu_object( $menu_obj, $menu_id ) {
    1989         $ok = (
    1990             get_current_blog_id() === $this->_previewed_blog_id
    1991             &&
    1992             is_int( $menu_id )
    1993             &&
    1994             $menu_id === $this->term_id
    1995         );
    1996         if ( ! $ok ) {
    1997             return $menu_obj;
    1998         }
    1999 
    2000         $setting_value = $this->value();
    2001 
    2002         // Handle deleted menus.
    2003         if ( false === $setting_value ) {
    2004             return false;
    2005         }
    2006 
    2007         // Handle sanitization failure by preventing short-circuiting.
    2008         if ( null === $setting_value ) {
    2009             return $menu_obj;
    2010         }
    2011 
    2012         $menu_obj = (object) array_merge( array(
    2013                 'term_id'          => $this->term_id,
    2014                 'term_taxonomy_id' => $this->term_id,
    2015                 'slug'             => sanitize_title( $setting_value['name'] ),
    2016                 'count'            => 0,
    2017                 'term_group'       => 0,
    2018                 'taxonomy'         => self::TAXONOMY,
    2019                 'filter'           => 'raw',
    2020             ), $setting_value );
    2021 
    2022         return $menu_obj;
    2023     }
    2024 
    2025     /**
    2026      * Filter the nav_menu_options option to include this menu's auto_add preference.
    2027      *
    2028      * @since 4.3.0
    2029      * @access public
    2030      *
    2031      * @param array $nav_menu_options Nav menu options including auto_add.
    2032      * @return array (Kaybe) modified nav menu options.
    2033      */
    2034     public function filter_nav_menu_options( $nav_menu_options ) {
    2035         if ( $this->_previewed_blog_id !== get_current_blog_id() ) {
    2036             return $nav_menu_options;
    2037         }
    2038 
    2039         $menu = $this->value();
    2040         $nav_menu_options = $this->filter_nav_menu_options_value(
    2041             $nav_menu_options,
    2042             $this->term_id,
    2043             false === $menu ? false : $menu['auto_add']
    2044         );
    2045 
    2046         return $nav_menu_options;
    2047     }
    2048 
    2049     /**
    2050      * Sanitize an input.
    2051      *
    2052      * Note that parent::sanitize() erroneously does wp_unslash() on $value, but
    2053      * we remove that in this override.
    2054      *
    2055      * @since 4.3.0
    2056      * @access public
    2057      *
    2058      * @param array $value The value to sanitize.
    2059      * @return array|false|null Null if an input isn't valid. False if it is marked for deletion.
    2060      *                          Otherwise the sanitized value.
    2061      */
    2062     public function sanitize( $value ) {
    2063         // Menu is marked for deletion.
    2064         if ( false === $value ) {
    2065             return $value;
    2066         }
    2067 
    2068         // Invalid.
    2069         if ( ! is_array( $value ) ) {
    2070             return null;
    2071         }
    2072 
    2073         $default = array(
    2074             'name'        => '',
    2075             'description' => '',
    2076             'parent'      => 0,
    2077             'auto_add'    => false,
    2078         );
    2079         $value = array_merge( $default, $value );
    2080         $value = wp_array_slice_assoc( $value, array_keys( $default ) );
    2081 
    2082         $value['name']        = trim( esc_html( $value['name'] ) ); // This sanitization code is used in wp-admin/nav-menus.php.
    2083         $value['description'] = sanitize_text_field( $value['description'] );
    2084         $value['parent']      = max( 0, intval( $value['parent'] ) );
    2085         $value['auto_add']    = ! empty( $value['auto_add'] );
    2086 
    2087         if ( '' === $value['name'] ) {
    2088             $value['name'] = _x( '(unnamed)', 'Missing menu name.' );
    2089         }
    2090 
    2091         /** This filter is documented in wp-includes/class-wp-customize-setting.php */
    2092         return apply_filters( "customize_sanitize_{$this->id}", $value, $this );
    2093     }
    2094 
    2095     /**
    2096      * Storage for data to be sent back to client in customize_save_response filter.
    2097      *
    2098      * @access protected
    2099      * @since 4.3.0
    2100      * @var array
    2101      *
    2102      * @see WP_Customize_Nav_Menu_Setting::amend_customize_save_response()
    2103      */
    2104     protected $_widget_nav_menu_updates = array();
    2105 
    2106     /**
    2107      * Create/update the nav_menu term for this setting.
    2108      *
    2109      * Any created menus will have their assigned term IDs exported to the client
    2110      * via the customize_save_response filter. Likewise, any errors will be exported
    2111      * to the client via the customize_save_response() filter.
    2112      *
    2113      * To delete a menu, the client can send false as the value.
    2114      *
    2115      * @since 4.3.0
    2116      * @access protected
    2117      *
    2118      * @see wp_update_nav_menu_object()
    2119      *
    2120      * @param array|false $value {
    2121      *     The value to update. Note that slug cannot be updated via wp_update_nav_menu_object().
    2122      *     If false, then the menu will be deleted entirely.
    2123      *
    2124      *     @type string $name        The name of the menu to save.
    2125      *     @type string $description The term description. Default empty string.
    2126      *     @type int    $parent      The id of the parent term. Default 0.
    2127      *     @type bool   $auto_add    Whether pages will auto_add to this menu. Default false.
    2128      * }
    2129      * @return null|void
    2130      */
    2131     protected function update( $value ) {
    2132         if ( $this->is_updated ) {
    2133             return;
    2134         }
    2135 
    2136         $this->is_updated = true;
    2137         $is_placeholder   = ( $this->term_id < 0 );
    2138         $is_delete        = ( false === $value );
    2139 
    2140         add_filter( 'customize_save_response', array( $this, 'amend_customize_save_response' ) );
    2141 
    2142         $auto_add = null;
    2143         if ( $is_delete ) {
    2144             // If the current setting term is a placeholder, a delete request is a no-op.
    2145             if ( $is_placeholder ) {
    2146                 $this->update_status = 'deleted';
    2147             } else {
    2148                 $r = wp_delete_nav_menu( $this->term_id );
    2149 
    2150                 if ( is_wp_error( $r ) ) {
    2151                     $this->update_status = 'error';
    2152                     $this->update_error  = $r;
    2153                 } else {
    2154                     $this->update_status = 'deleted';
    2155                     $auto_add = false;
    2156                 }
    2157             }
    2158         } else {
    2159             // Insert or update menu.
    2160             $menu_data = wp_array_slice_assoc( $value, array( 'description', 'parent' ) );
    2161             $menu_data['menu-name'] = $value['name'];
    2162 
    2163             $menu_id = $is_placeholder ? 0 : $this->term_id;
    2164             $r = wp_update_nav_menu_object( $menu_id, $menu_data );
    2165             $original_name = $menu_data['menu-name'];
    2166             $name_conflict_suffix = 1;
    2167             while ( is_wp_error( $r ) && 'menu_exists' === $r->get_error_code() ) {
    2168                 $name_conflict_suffix += 1;
    2169                 /* translators: 1: original menu name, 2: duplicate count */
    2170                 $menu_data['menu-name'] = sprintf( __( '%1$s (%2$d)' ), $original_name, $name_conflict_suffix );
    2171                 $r = wp_update_nav_menu_object( $menu_id, $menu_data );
    2172             }
    2173 
    2174             if ( is_wp_error( $r ) ) {
    2175                 $this->update_status = 'error';
    2176                 $this->update_error  = $r;
    2177             } else {
    2178                 if ( $is_placeholder ) {
    2179                     $this->previous_term_id = $this->term_id;
    2180                     $this->term_id          = $r;
    2181                     $this->update_status    = 'inserted';
    2182                 } else {
    2183                     $this->update_status = 'updated';
    2184                 }
    2185 
    2186                 $auto_add = $value['auto_add'];
    2187             }
    2188         }
    2189 
    2190         if ( null !== $auto_add ) {
    2191             $nav_menu_options = $this->filter_nav_menu_options_value(
    2192                 (array) get_option( 'nav_menu_options', array() ),
    2193                 $this->term_id,
    2194                 $auto_add
    2195             );
    2196             update_option( 'nav_menu_options', $nav_menu_options );
    2197         }
    2198 
    2199         if ( 'inserted' === $this->update_status ) {
    2200             // Make sure that new menus assigned to nav menu locations use their new IDs.
    2201             foreach ( $this->manager->settings() as $setting ) {
    2202                 if ( ! preg_match( '/^nav_menu_locations\[/', $setting->id ) ) {
    2203                     continue;
    2204                 }
    2205 
    2206                 $post_value = $setting->post_value( null );
    2207                 if ( ! is_null( $post_value ) && $this->previous_term_id === intval( $post_value ) ) {
    2208                     $this->manager->set_post_value( $setting->id, $this->term_id );
    2209                     $setting->save();
    2210                 }
    2211             }
    2212 
    2213             // Make sure that any nav_menu widgets referencing the placeholder nav menu get updated and sent back to client.
    2214             foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) {
    2215                 $nav_menu_widget_setting = $this->manager->get_setting( $setting_id );
    2216                 if ( ! $nav_menu_widget_setting || ! preg_match( '/^widget_nav_menu\[/', $nav_menu_widget_setting->id ) ) {
    2217                     continue;
    2218                 }
    2219 
    2220                 $widget_instance = $nav_menu_widget_setting->post_value(); // Note that this calls WP_Customize_Widgets::sanitize_widget_instance().
    2221                 if ( empty( $widget_instance['nav_menu'] ) || intval( $widget_instance['nav_menu'] ) !== $this->previous_term_id ) {
    2222                     continue;
    2223                 }
    2224 
    2225                 $widget_instance['nav_menu'] = $this->term_id;
    2226                 $updated_widget_instance = $this->manager->widgets->sanitize_widget_js_instance( $widget_instance );
    2227                 $this->manager->set_post_value( $nav_menu_widget_setting->id, $updated_widget_instance );
    2228                 $nav_menu_widget_setting->save();
    2229 
    2230                 $this->_widget_nav_menu_updates[ $nav_menu_widget_setting->id ] = $updated_widget_instance;
    2231             }
    2232         }
    2233     }
    2234 
    2235     /**
    2236      * Updates a nav_menu_options array.
    2237      *
    2238      * @since 4.3.0
    2239      * @access protected
    2240      *
    2241      * @see WP_Customize_Nav_Menu_Setting::filter_nav_menu_options()
    2242      * @see WP_Customize_Nav_Menu_Setting::update()
    2243      *
    2244      * @param array $nav_menu_options Array as returned by get_option( 'nav_menu_options' ).
    2245      * @param int   $menu_id          The term ID for the given menu.
    2246      * @param bool  $auto_add         Whether to auto-add or not.
    2247      * @return array (Maybe) modified nav_menu_otions array.
    2248      */
    2249     protected function filter_nav_menu_options_value( $nav_menu_options, $menu_id, $auto_add ) {
    2250         $nav_menu_options = (array) $nav_menu_options;
    2251         if ( ! isset( $nav_menu_options['auto_add'] ) ) {
    2252             $nav_menu_options['auto_add'] = array();
    2253         }
    2254 
    2255         $i = array_search( $menu_id, $nav_menu_options['auto_add'] );
    2256         if ( $auto_add && false === $i ) {
    2257             array_push( $nav_menu_options['auto_add'], $this->term_id );
    2258         } elseif ( ! $auto_add && false !== $i ) {
    2259             array_splice( $nav_menu_options['auto_add'], $i, 1 );
    2260         }
    2261 
    2262         return $nav_menu_options;
    2263     }
    2264 
    2265     /**
    2266      * Export data for the JS client.
    2267      *
    2268      * @since 4.3.0
    2269      * @access public
    2270      *
    2271      * @see WP_Customize_Nav_Menu_Setting::update()
    2272      *
    2273      * @param array $data Additional information passed back to the 'saved' event on `wp.customize`.
    2274      * @return array Export data.
    2275      */
    2276     public function amend_customize_save_response( $data ) {
    2277         if ( ! isset( $data['nav_menu_updates'] ) ) {
    2278             $data['nav_menu_updates'] = array();
    2279         }
    2280         if ( ! isset( $data['widget_nav_menu_updates'] ) ) {
    2281             $data['widget_nav_menu_updates'] = array();
    2282         }
    2283 
    2284         $data['nav_menu_updates'][] = array(
    2285             'term_id'          => $this->term_id,
    2286             'previous_term_id' => $this->previous_term_id,
    2287             'error'            => $this->update_error ? $this->update_error->get_error_code() : null,
    2288             'status'           => $this->update_status,
    2289             'saved_value'      => 'deleted' === $this->update_status ? null : $this->value(),
    2290         );
    2291 
    2292         $data['widget_nav_menu_updates'] = array_merge(
    2293             $data['widget_nav_menu_updates'],
    2294             $this->_widget_nav_menu_updates
    2295         );
    2296         $this->_widget_nav_menu_updates = array();
    2297 
    2298         return $data;
    2299     }
    2300 }
Note: See TracChangeset for help on using the changeset viewer.