wp/wp-includes/class-wp-customize-widgets.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
    20 
    20 
    21 	/**
    21 	/**
    22 	 * WP_Customize_Manager instance.
    22 	 * WP_Customize_Manager instance.
    23 	 *
    23 	 *
    24 	 * @since 3.9.0
    24 	 * @since 3.9.0
    25 	 * @access public
       
    26 	 * @var WP_Customize_Manager
    25 	 * @var WP_Customize_Manager
    27 	 */
    26 	 */
    28 	public $manager;
    27 	public $manager;
    29 
    28 
    30 	/**
    29 	/**
    31 	 * All id_bases for widgets defined in core.
    30 	 * All id_bases for widgets defined in core.
    32 	 *
    31 	 *
    33 	 * @since 3.9.0
    32 	 * @since 3.9.0
    34 	 * @access protected
       
    35 	 * @var array
    33 	 * @var array
    36 	 */
    34 	 */
    37 	protected $core_widget_id_bases = array(
    35 	protected $core_widget_id_bases = array(
    38 		'archives', 'calendar', 'categories', 'links', 'meta',
    36 		'archives',
    39 		'nav_menu', 'pages', 'recent-comments', 'recent-posts',
    37 		'calendar',
    40 		'rss', 'search', 'tag_cloud', 'text',
    38 		'categories',
       
    39 		'custom_html',
       
    40 		'links',
       
    41 		'media_audio',
       
    42 		'media_image',
       
    43 		'media_video',
       
    44 		'meta',
       
    45 		'nav_menu',
       
    46 		'pages',
       
    47 		'recent-comments',
       
    48 		'recent-posts',
       
    49 		'rss',
       
    50 		'search',
       
    51 		'tag_cloud',
       
    52 		'text',
    41 	);
    53 	);
    42 
    54 
    43 	/**
    55 	/**
    44 	 * @since 3.9.0
    56 	 * @since 3.9.0
    45 	 * @access protected
       
    46 	 * @var array
    57 	 * @var array
    47 	 */
    58 	 */
    48 	protected $rendered_sidebars = array();
    59 	protected $rendered_sidebars = array();
    49 
    60 
    50 	/**
    61 	/**
    51 	 * @since 3.9.0
    62 	 * @since 3.9.0
    52 	 * @access protected
       
    53 	 * @var array
    63 	 * @var array
    54 	 */
    64 	 */
    55 	protected $rendered_widgets = array();
    65 	protected $rendered_widgets = array();
    56 
    66 
    57 	/**
    67 	/**
    58 	 * @since 3.9.0
    68 	 * @since 3.9.0
    59 	 * @access protected
       
    60 	 * @var array
    69 	 * @var array
    61 	 */
    70 	 */
    62 	protected $old_sidebars_widgets = array();
    71 	protected $old_sidebars_widgets = array();
    63 
    72 
    64 	/**
    73 	/**
       
    74 	 * Mapping of widget ID base to whether it supports selective refresh.
       
    75 	 *
       
    76 	 * @since 4.5.0
       
    77 	 * @var array
       
    78 	 */
       
    79 	protected $selective_refreshable_widgets;
       
    80 
       
    81 	/**
    65 	 * Mapping of setting type to setting ID pattern.
    82 	 * Mapping of setting type to setting ID pattern.
    66 	 *
    83 	 *
    67 	 * @since 4.2.0
    84 	 * @since 4.2.0
    68 	 * @access protected
       
    69 	 * @var array
    85 	 * @var array
    70 	 */
    86 	 */
    71 	protected $setting_id_patterns = array(
    87 	protected $setting_id_patterns = array(
    72 		'widget_instance' => '/^(widget_.+?)(?:\[(\d+)\])?$/',
    88 		'widget_instance' => '/^widget_(?P<id_base>.+?)(?:\[(?P<widget_number>\d+)\])?$/',
    73 		'sidebar_widgets' => '/^sidebars_widgets\[(.+?)\]$/',
    89 		'sidebar_widgets' => '/^sidebars_widgets\[(?P<sidebar_id>.+?)\]$/',
    74 	);
    90 	);
    75 
    91 
    76 	/**
    92 	/**
    77 	 * Initial loader.
    93 	 * Initial loader.
    78 	 *
    94 	 *
    79 	 * @since 3.9.0
    95 	 * @since 3.9.0
    80 	 * @access public
       
    81 	 *
    96 	 *
    82 	 * @param WP_Customize_Manager $manager Customize manager bootstrap instance.
    97 	 * @param WP_Customize_Manager $manager Customize manager bootstrap instance.
    83 	 */
    98 	 */
    84 	public function __construct( $manager ) {
    99 	public function __construct( $manager ) {
    85 		$this->manager = $manager;
   100 		$this->manager = $manager;
    86 
   101 
       
   102 		// See https://github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L420-L449
    87 		add_filter( 'customize_dynamic_setting_args',          array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
   103 		add_filter( 'customize_dynamic_setting_args',          array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
    88 		add_action( 'after_setup_theme',                       array( $this, 'register_settings' ) );
   104 		add_action( 'widgets_init',                            array( $this, 'register_settings' ), 95 );
       
   105 		add_action( 'customize_register',                      array( $this, 'schedule_customize_register' ), 1 );
       
   106 
       
   107 		// Skip remaining hooks when the user can't manage widgets anyway.
       
   108 		if ( ! current_user_can( 'edit_theme_options' ) ) {
       
   109 			return;
       
   110 		}
       
   111 
    89 		add_action( 'wp_loaded',                               array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
   112 		add_action( 'wp_loaded',                               array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
    90 		add_action( 'customize_controls_init',                 array( $this, 'customize_controls_init' ) );
   113 		add_action( 'customize_controls_init',                 array( $this, 'customize_controls_init' ) );
    91 		add_action( 'customize_register',                      array( $this, 'schedule_customize_register' ), 1 );
       
    92 		add_action( 'customize_controls_enqueue_scripts',      array( $this, 'enqueue_scripts' ) );
   114 		add_action( 'customize_controls_enqueue_scripts',      array( $this, 'enqueue_scripts' ) );
    93 		add_action( 'customize_controls_print_styles',         array( $this, 'print_styles' ) );
   115 		add_action( 'customize_controls_print_styles',         array( $this, 'print_styles' ) );
    94 		add_action( 'customize_controls_print_scripts',        array( $this, 'print_scripts' ) );
   116 		add_action( 'customize_controls_print_scripts',        array( $this, 'print_scripts' ) );
    95 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_footer_scripts' ) );
   117 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_footer_scripts' ) );
    96 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) );
   118 		add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) );
    98 		add_filter( 'customize_refresh_nonces',                array( $this, 'refresh_nonces' ) );
   120 		add_filter( 'customize_refresh_nonces',                array( $this, 'refresh_nonces' ) );
    99 
   121 
   100 		add_action( 'dynamic_sidebar',                         array( $this, 'tally_rendered_widgets' ) );
   122 		add_action( 'dynamic_sidebar',                         array( $this, 'tally_rendered_widgets' ) );
   101 		add_filter( 'is_active_sidebar',                       array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
   123 		add_filter( 'is_active_sidebar',                       array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
   102 		add_filter( 'dynamic_sidebar_has_widgets',             array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
   124 		add_filter( 'dynamic_sidebar_has_widgets',             array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
   103 	}
   125 
   104 
   126 		// Selective Refresh.
   105 	/**
   127 		add_filter( 'customize_dynamic_partial_args',          array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
   106 	 * Get the widget setting type given a setting ID.
   128 		add_action( 'customize_preview_init',                  array( $this, 'selective_refresh_init' ) );
       
   129 	}
       
   130 
       
   131 	/**
       
   132 	 * List whether each registered widget can be use selective refresh.
       
   133 	 *
       
   134 	 * If the theme does not support the customize-selective-refresh-widgets feature,
       
   135 	 * then this will always return an empty array.
       
   136 	 *
       
   137 	 * @since 4.5.0
       
   138 	 *
       
   139 	 * @global WP_Widget_Factory $wp_widget_factory
       
   140 	 *
       
   141 	 * @return array Mapping of id_base to support. If theme doesn't support
       
   142 	 *               selective refresh, an empty array is returned.
       
   143 	 */
       
   144 	public function get_selective_refreshable_widgets() {
       
   145 		global $wp_widget_factory;
       
   146 		if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
       
   147 			return array();
       
   148 		}
       
   149 		if ( ! isset( $this->selective_refreshable_widgets ) ) {
       
   150 			$this->selective_refreshable_widgets = array();
       
   151 			foreach ( $wp_widget_factory->widgets as $wp_widget ) {
       
   152 				$this->selective_refreshable_widgets[ $wp_widget->id_base ] = ! empty( $wp_widget->widget_options['customize_selective_refresh'] );
       
   153 			}
       
   154 		}
       
   155 		return $this->selective_refreshable_widgets;
       
   156 	}
       
   157 
       
   158 	/**
       
   159 	 * Determines if a widget supports selective refresh.
       
   160 	 *
       
   161 	 * @since 4.5.0
       
   162 	 *
       
   163 	 * @param string $id_base Widget ID Base.
       
   164 	 * @return bool Whether the widget can be selective refreshed.
       
   165 	 */
       
   166 	public function is_widget_selective_refreshable( $id_base ) {
       
   167 		$selective_refreshable_widgets = $this->get_selective_refreshable_widgets();
       
   168 		return ! empty( $selective_refreshable_widgets[ $id_base ] );
       
   169 	}
       
   170 
       
   171 	/**
       
   172 	 * Retrieves the widget setting type given a setting ID.
   107 	 *
   173 	 *
   108 	 * @since 4.2.0
   174 	 * @since 4.2.0
   109 	 * @access protected
   175 	 *
   110 	 *
   176 	 * @staticvar array $cache
   111 	 * @param $setting_id Setting ID.
   177 	 *
   112 	 * @return string|null Setting type. Null otherwise.
   178 	 * @param string $setting_id Setting ID.
       
   179 	 * @return string|void Setting type.
   113 	 */
   180 	 */
   114 	protected function get_setting_type( $setting_id ) {
   181 	protected function get_setting_type( $setting_id ) {
   115 		static $cache = array();
   182 		static $cache = array();
   116 		if ( isset( $cache[ $setting_id ] ) ) {
   183 		if ( isset( $cache[ $setting_id ] ) ) {
   117 			return $cache[ $setting_id ];
   184 			return $cache[ $setting_id ];
   120 			if ( preg_match( $pattern, $setting_id ) ) {
   187 			if ( preg_match( $pattern, $setting_id ) ) {
   121 				$cache[ $setting_id ] = $type;
   188 				$cache[ $setting_id ] = $type;
   122 				return $type;
   189 				return $type;
   123 			}
   190 			}
   124 		}
   191 		}
   125 		return null;
   192 	}
   126 	}
   193 
   127 
   194 	/**
   128 	/**
   195 	 * Inspects the incoming customized data for any widget settings, and dynamically adds
   129 	 * Inspect the incoming customized data for any widget settings, and dynamically add them up-front so widgets will be initialized properly.
   196 	 * them up-front so widgets will be initialized properly.
   130 	 *
   197 	 *
   131 	 * @since 4.2.0
   198 	 * @since 4.2.0
   132 	 * @access public
       
   133 	 */
   199 	 */
   134 	public function register_settings() {
   200 	public function register_settings() {
   135 		$widget_setting_ids = array();
   201 		$widget_setting_ids = array();
   136 		$incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
   202 		$incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
   137 		foreach ( $incoming_setting_ids as $setting_id ) {
   203 		foreach ( $incoming_setting_ids as $setting_id ) {
   143 			$widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
   209 			$widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
   144 		}
   210 		}
   145 
   211 
   146 		$settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
   212 		$settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
   147 
   213 
   148 		/*
   214 		if ( $this->manager->settings_previewed() ) {
   149 		 * Preview settings right away so that widgets and sidebars will get registered properly.
       
   150 		 * But don't do this if a customize_save because this will cause WP to think there is nothing
       
   151 		 * changed that needs to be saved.
       
   152 		 */
       
   153 		if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
       
   154 			foreach ( $settings as $setting ) {
   215 			foreach ( $settings as $setting ) {
   155 				$setting->preview();
   216 				$setting->preview();
   156 			}
   217 			}
   157 		}
   218 		}
   158 	}
   219 	}
   159 
   220 
   160 	/**
   221 	/**
   161 	 * Determine the arguments for a dynamically-created setting.
   222 	 * Determines the arguments for a dynamically-created setting.
   162 	 *
   223 	 *
   163 	 * @since 4.2.0
   224 	 * @since 4.2.0
   164 	 * @access public
   225 	 *
   165 	 *
   226 	 * @param false|array $args       The arguments to the WP_Customize_Setting constructor.
   166 	 * @param false|array $setting_args The arguments to the WP_Customize_Setting constructor.
   227 	 * @param string      $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
   167 	 * @param string      $setting_id   ID for dynamic setting, usually coming from `$_POST['customized']`.
       
   168 	 * @return false|array Setting arguments, false otherwise.
   228 	 * @return false|array Setting arguments, false otherwise.
   169 	 */
   229 	 */
   170 	public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
   230 	public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
   171 		if ( $this->get_setting_type( $setting_id ) ) {
   231 		if ( $this->get_setting_type( $setting_id ) ) {
   172 			$args = $this->get_setting_args( $setting_id );
   232 			$args = $this->get_setting_args( $setting_id );
   173 		}
   233 		}
   174 		return $args;
   234 		return $args;
   175 	}
   235 	}
   176 
   236 
   177 	/**
   237 	/**
   178 	 * Get an unslashed post value or return a default.
   238 	 * Retrieves an unslashed post value or return a default.
   179 	 *
   239 	 *
   180 	 * @since 3.9.0
   240 	 * @since 3.9.0
   181 	 *
       
   182 	 * @access protected
       
   183 	 *
   241 	 *
   184 	 * @param string $name    Post value.
   242 	 * @param string $name    Post value.
   185 	 * @param mixed  $default Default post value.
   243 	 * @param mixed  $default Default post value.
   186 	 * @return mixed Unslashed post value or default value.
   244 	 * @return mixed Unslashed post value or default value.
   187 	 */
   245 	 */
   201 	 * setting. Also store the old theme's existing settings so that they can
   259 	 * setting. Also store the old theme's existing settings so that they can
   202 	 * be passed along for storing in the sidebars_widgets theme_mod when the
   260 	 * be passed along for storing in the sidebars_widgets theme_mod when the
   203 	 * theme gets switched.
   261 	 * theme gets switched.
   204 	 *
   262 	 *
   205 	 * @since 3.9.0
   263 	 * @since 3.9.0
   206 	 * @access public
   264 	 *
       
   265 	 * @global array $sidebars_widgets
       
   266 	 * @global array $_wp_sidebars_widgets
   207 	 */
   267 	 */
   208 	public function override_sidebars_widgets_for_theme_switch() {
   268 	public function override_sidebars_widgets_for_theme_switch() {
   209 		global $sidebars_widgets;
   269 		global $sidebars_widgets;
   210 
   270 
   211 		if ( $this->manager->doing_ajax() || $this->manager->is_theme_active() ) {
   271 		if ( $this->manager->doing_ajax() || $this->manager->is_theme_active() ) {
   212 			return;
   272 			return;
   213 		}
   273 		}
   214 
   274 
   215 		$this->old_sidebars_widgets = wp_get_sidebars_widgets();
   275 		$this->old_sidebars_widgets = wp_get_sidebars_widgets();
   216 		add_filter( 'customize_value_old_sidebars_widgets_data', array( $this, 'filter_customize_value_old_sidebars_widgets_data' ) );
   276 		add_filter( 'customize_value_old_sidebars_widgets_data', array( $this, 'filter_customize_value_old_sidebars_widgets_data' ) );
       
   277 		$this->manager->set_post_value( 'old_sidebars_widgets_data', $this->old_sidebars_widgets ); // Override any value cached in changeset.
   217 
   278 
   218 		// retrieve_widgets() looks at the global $sidebars_widgets
   279 		// retrieve_widgets() looks at the global $sidebars_widgets
   219 		$sidebars_widgets = $this->old_sidebars_widgets;
   280 		$sidebars_widgets = $this->old_sidebars_widgets;
   220 		$sidebars_widgets = retrieve_widgets( 'customize' );
   281 		$sidebars_widgets = retrieve_widgets( 'customize' );
   221 		add_filter( 'option_sidebars_widgets', array( $this, 'filter_option_sidebars_widgets_for_theme_switch' ), 1 );
   282 		add_filter( 'option_sidebars_widgets', array( $this, 'filter_option_sidebars_widgets_for_theme_switch' ), 1 );
   222 		unset( $GLOBALS['_wp_sidebars_widgets'] ); // reset global cache var used by wp_get_sidebars_widgets()
   283 		// reset global cache var used by wp_get_sidebars_widgets()
   223 	}
   284 		unset( $GLOBALS['_wp_sidebars_widgets'] );
   224 
   285 	}
   225 	/**
   286 
   226 	 * Filter old_sidebars_widgets_data Customizer setting.
   287 	/**
   227 	 *
   288 	 * Filters old_sidebars_widgets_data Customizer setting.
   228 	 * When switching themes, filter the Customizer setting
   289 	 *
   229 	 * old_sidebars_widgets_data to supply initial $sidebars_widgets before they
   290 	 * When switching themes, filter the Customizer setting old_sidebars_widgets_data
   230 	 * were overridden by retrieve_widgets(). The value for
   291 	 * to supply initial $sidebars_widgets before they were overridden by retrieve_widgets().
   231 	 * old_sidebars_widgets_data gets set in the old theme's sidebars_widgets
   292 	 * The value for old_sidebars_widgets_data gets set in the old theme's sidebars_widgets
   232 	 * theme_mod.
   293 	 * theme_mod.
   233 	 *
   294 	 *
       
   295 	 * @since 3.9.0
       
   296 	 *
   234 	 * @see WP_Customize_Widgets::handle_theme_switch()
   297 	 * @see WP_Customize_Widgets::handle_theme_switch()
   235 	 * @since 3.9.0
       
   236 	 * @access public
       
   237 	 *
   298 	 *
   238 	 * @param array $old_sidebars_widgets
   299 	 * @param array $old_sidebars_widgets
       
   300 	 * @return array
   239 	 */
   301 	 */
   240 	public function filter_customize_value_old_sidebars_widgets_data( $old_sidebars_widgets ) {
   302 	public function filter_customize_value_old_sidebars_widgets_data( $old_sidebars_widgets ) {
   241 		return $this->old_sidebars_widgets;
   303 		return $this->old_sidebars_widgets;
   242 	}
   304 	}
   243 
   305 
   244 	/**
   306 	/**
   245 	 * Filter sidebars_widgets option for theme switch.
   307 	 * Filters sidebars_widgets option for theme switch.
   246 	 *
   308 	 *
   247 	 * When switching themes, the retrieve_widgets() function is run when the
   309 	 * When switching themes, the retrieve_widgets() function is run when the Customizer initializes,
   248 	 * Customizer initializes, and then the new sidebars_widgets here get
   310 	 * and then the new sidebars_widgets here get supplied as the default value for the sidebars_widgets
   249 	 * supplied as the default value for the sidebars_widgets option.
   311 	 * option.
       
   312 	 *
       
   313 	 * @since 3.9.0
   250 	 *
   314 	 *
   251 	 * @see WP_Customize_Widgets::handle_theme_switch()
   315 	 * @see WP_Customize_Widgets::handle_theme_switch()
   252 	 * @since 3.9.0
   316 	 * @global array $sidebars_widgets
   253 	 * @access public
       
   254 	 *
   317 	 *
   255 	 * @param array $sidebars_widgets
   318 	 * @param array $sidebars_widgets
       
   319 	 * @return array
   256 	 */
   320 	 */
   257 	public function filter_option_sidebars_widgets_for_theme_switch( $sidebars_widgets ) {
   321 	public function filter_option_sidebars_widgets_for_theme_switch( $sidebars_widgets ) {
   258 		$sidebars_widgets = $GLOBALS['sidebars_widgets'];
   322 		$sidebars_widgets = $GLOBALS['sidebars_widgets'];
   259 		$sidebars_widgets['array_version'] = 3;
   323 		$sidebars_widgets['array_version'] = 3;
   260 		return $sidebars_widgets;
   324 		return $sidebars_widgets;
   261 	}
   325 	}
   262 
   326 
   263 	/**
   327 	/**
   264 	 * Make sure all widgets get loaded into the Customizer.
   328 	 * Ensures all widgets get loaded into the Customizer.
   265 	 *
   329 	 *
   266 	 * Note: these actions are also fired in wp_ajax_update_widget().
   330 	 * Note: these actions are also fired in wp_ajax_update_widget().
   267 	 *
   331 	 *
   268 	 * @since 3.9.0
   332 	 * @since 3.9.0
   269 	 * @access public
       
   270 	 */
   333 	 */
   271 	public function customize_controls_init() {
   334 	public function customize_controls_init() {
   272 		/** This action is documented in wp-admin/includes/ajax-actions.php */
   335 		/** This action is documented in wp-admin/includes/ajax-actions.php */
   273 		do_action( 'load-widgets.php' );
   336 		do_action( 'load-widgets.php' );
   274 
   337 
   278 		/** This action is documented in wp-admin/widgets.php */
   341 		/** This action is documented in wp-admin/widgets.php */
   279 		do_action( 'sidebar_admin_setup' );
   342 		do_action( 'sidebar_admin_setup' );
   280 	}
   343 	}
   281 
   344 
   282 	/**
   345 	/**
   283 	 * Ensure widgets are available for all types of previews.
   346 	 * Ensures widgets are available for all types of previews.
   284 	 *
   347 	 *
   285 	 * When in preview, hook to 'customize_register' for settings
   348 	 * When in preview, hook to {@see 'customize_register'} for settings after WordPress is loaded
   286 	 * after WordPress is loaded so that all filters have been
   349 	 * so that all filters have been initialized (e.g. Widget Visibility).
   287 	 * initialized (e.g. Widget Visibility).
   350 	 *
   288 	 *
   351 	 * @since 3.9.0
   289 	 * @since 3.9.0
       
   290 	 * @access public
       
   291 	 */
   352 	 */
   292 	public function schedule_customize_register() {
   353 	public function schedule_customize_register() {
   293 		if ( is_admin() ) {
   354 		if ( is_admin() ) {
   294 			$this->customize_register();
   355 			$this->customize_register();
   295 		} else {
   356 		} else {
   296 			add_action( 'wp', array( $this, 'customize_register' ) );
   357 			add_action( 'wp', array( $this, 'customize_register' ) );
   297 		}
   358 		}
   298 	}
   359 	}
   299 
   360 
   300 	/**
   361 	/**
   301 	 * Register Customizer settings and controls for all sidebars and widgets.
   362 	 * Registers Customizer settings and controls for all sidebars and widgets.
   302 	 *
   363 	 *
   303 	 * @since 3.9.0
   364 	 * @since 3.9.0
   304 	 * @access public
   365 	 *
       
   366 	 * @global array $wp_registered_widgets
       
   367 	 * @global array $wp_registered_widget_controls
       
   368 	 * @global array $wp_registered_sidebars
   305 	 */
   369 	 */
   306 	public function customize_register() {
   370 	public function customize_register() {
   307 		global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars;
   371 		global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars;
   308 
   372 
       
   373 		add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
       
   374 
   309 		$sidebars_widgets = array_merge(
   375 		$sidebars_widgets = array_merge(
   310 			array( 'wp_inactive_widgets' => array() ),
   376 			array( 'wp_inactive_widgets' => array() ),
   311 			array_fill_keys( array_keys( $GLOBALS['wp_registered_sidebars'] ), array() ),
   377 			array_fill_keys( array_keys( $wp_registered_sidebars ), array() ),
   312 			wp_get_sidebars_widgets()
   378 			wp_get_sidebars_widgets()
   313 		);
   379 		);
   314 
   380 
   315 		$new_setting_ids = array();
   381 		$new_setting_ids = array();
   316 
   382 
   328 			$new_setting_ids[] = $setting_id;
   394 			$new_setting_ids[] = $setting_id;
   329 		}
   395 		}
   330 
   396 
   331 		/*
   397 		/*
   332 		 * Add a setting which will be supplied for the theme's sidebars_widgets
   398 		 * Add a setting which will be supplied for the theme's sidebars_widgets
   333 		 * theme_mod when the the theme is switched.
   399 		 * theme_mod when the theme is switched.
   334 		 */
   400 		 */
   335 		if ( ! $this->manager->is_theme_active() ) {
   401 		if ( ! $this->manager->is_theme_active() ) {
   336 			$setting_id = 'old_sidebars_widgets_data';
   402 			$setting_id = 'old_sidebars_widgets_data';
   337 			$setting_args = $this->get_setting_args( $setting_id, array(
   403 			$setting_args = $this->get_setting_args( $setting_id, array(
   338 				'type' => 'global_variable',
   404 				'type' => 'global_variable',
   340 			) );
   406 			) );
   341 			$this->manager->add_setting( $setting_id, $setting_args );
   407 			$this->manager->add_setting( $setting_id, $setting_args );
   342 		}
   408 		}
   343 
   409 
   344 		$this->manager->add_panel( 'widgets', array(
   410 		$this->manager->add_panel( 'widgets', array(
   345 			'title'       => __( 'Widgets' ),
   411 			'type'            => 'widgets',
   346 			'description' => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
   412 			'title'           => __( 'Widgets' ),
   347 			'priority'    => 110,
   413 			'description'     => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
       
   414 			'priority'        => 110,
       
   415 			'active_callback' => array( $this, 'is_panel_active' ),
       
   416 			'auto_expand_sole_section' => true,
   348 		) );
   417 		) );
   349 
   418 
   350 		foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
   419 		foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
   351 			if ( empty( $sidebar_widget_ids ) ) {
   420 			if ( empty( $sidebar_widget_ids ) ) {
   352 				$sidebar_widget_ids = array();
   421 				$sidebar_widget_ids = array();
   353 			}
   422 			}
   354 
   423 
   355 			$is_registered_sidebar = isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] );
   424 			$is_registered_sidebar = is_registered_sidebar( $sidebar_id );
   356 			$is_inactive_widgets   = ( 'wp_inactive_widgets' === $sidebar_id );
   425 			$is_inactive_widgets   = ( 'wp_inactive_widgets' === $sidebar_id );
   357 			$is_active_sidebar     = ( $is_registered_sidebar && ! $is_inactive_widgets );
   426 			$is_active_sidebar     = ( $is_registered_sidebar && ! $is_inactive_widgets );
   358 
   427 
   359 			// Add setting for managing the sidebar's widgets.
   428 			// Add setting for managing the sidebar's widgets.
   360 			if ( $is_registered_sidebar || $is_inactive_widgets ) {
   429 			if ( $is_registered_sidebar || $is_inactive_widgets ) {
   371 				// Add section to contain controls.
   440 				// Add section to contain controls.
   372 				$section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
   441 				$section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
   373 				if ( $is_active_sidebar ) {
   442 				if ( $is_active_sidebar ) {
   374 
   443 
   375 					$section_args = array(
   444 					$section_args = array(
   376 						'title' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['name'],
   445 						'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],
   377 						'description' => $GLOBALS['wp_registered_sidebars'][ $sidebar_id ]['description'],
   446 						'description' => $wp_registered_sidebars[ $sidebar_id ]['description'],
   378 						'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
   447 						'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ) ),
   379 						'panel' => 'widgets',
   448 						'panel' => 'widgets',
   380 						'sidebar_id' => $sidebar_id,
   449 						'sidebar_id' => $sidebar_id,
   381 					);
   450 					);
   382 
   451 
   383 					/**
   452 					/**
   384 					 * Filter Customizer widget section arguments for a given sidebar.
   453 					 * Filters Customizer widget section arguments for a given sidebar.
   385 					 *
   454 					 *
   386 					 * @since 3.9.0
   455 					 * @since 3.9.0
   387 					 *
   456 					 *
   388 					 * @param array      $section_args Array of Customizer widget section arguments.
   457 					 * @param array      $section_args Array of Customizer widget section arguments.
   389 					 * @param string     $section_id   Customizer section ID.
   458 					 * @param string     $section_id   Customizer section ID.
   407 
   476 
   408 			// Add a control for each active widget (located in a sidebar).
   477 			// Add a control for each active widget (located in a sidebar).
   409 			foreach ( $sidebar_widget_ids as $i => $widget_id ) {
   478 			foreach ( $sidebar_widget_ids as $i => $widget_id ) {
   410 
   479 
   411 				// Skip widgets that may have gone away due to a plugin being deactivated.
   480 				// Skip widgets that may have gone away due to a plugin being deactivated.
   412 				if ( ! $is_active_sidebar || ! isset( $GLOBALS['wp_registered_widgets'][$widget_id] ) ) {
   481 				if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[$widget_id] ) ) {
   413 					continue;
   482 					continue;
   414 				}
   483 				}
   415 
   484 
   416 				$registered_widget = $GLOBALS['wp_registered_widgets'][$widget_id];
   485 				$registered_widget = $wp_registered_widgets[$widget_id];
   417 				$setting_id        = $this->get_setting_id( $widget_id );
   486 				$setting_id        = $this->get_setting_id( $widget_id );
   418 				$id_base           = $GLOBALS['wp_registered_widget_controls'][$widget_id]['id_base'];
   487 				$id_base           = $wp_registered_widget_controls[$widget_id]['id_base'];
   419 
   488 
   420 				$control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
   489 				$control = new WP_Widget_Form_Customize_Control( $this->manager, $setting_id, array(
   421 					'label'          => $registered_widget['name'],
   490 					'label'          => $registered_widget['name'],
   422 					'section'        => $section_id,
   491 					'section'        => $section_id,
   423 					'sidebar_id'     => $sidebar_id,
   492 					'sidebar_id'     => $sidebar_id,
   430 				) );
   499 				) );
   431 				$this->manager->add_control( $control );
   500 				$this->manager->add_control( $control );
   432 			}
   501 			}
   433 		}
   502 		}
   434 
   503 
   435 		if ( ! $this->manager->doing_ajax( 'customize_save' ) ) {
   504 		if ( $this->manager->settings_previewed() ) {
   436 			foreach ( $new_setting_ids as $new_setting_id ) {
   505 			foreach ( $new_setting_ids as $new_setting_id ) {
   437 				$this->manager->get_setting( $new_setting_id )->preview();
   506 				$this->manager->get_setting( $new_setting_id )->preview();
   438 			}
   507 			}
   439 		}
   508 		}
   440 
   509 	}
   441 		add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
   510 
   442 	}
   511 	/**
   443 
   512 	 * Determines whether the widgets panel is active, based on whether there are sidebars registered.
   444 	/**
   513 	 *
   445 	 * Covert a widget_id into its corresponding Customizer setting ID (option name).
   514 	 * @since 4.4.0
   446 	 *
   515 	 *
   447 	 * @since 3.9.0
   516 	 * @see WP_Customize_Panel::$active_callback
   448 	 * @access public
   517 	 *
       
   518 	 * @global array $wp_registered_sidebars
       
   519 	 * @return bool Active.
       
   520 	 */
       
   521 	public function is_panel_active() {
       
   522 		global $wp_registered_sidebars;
       
   523 		return ! empty( $wp_registered_sidebars );
       
   524 	}
       
   525 
       
   526 	/**
       
   527 	 * Converts a widget_id into its corresponding Customizer setting ID (option name).
       
   528 	 *
       
   529 	 * @since 3.9.0
   449 	 *
   530 	 *
   450 	 * @param string $widget_id Widget ID.
   531 	 * @param string $widget_id Widget ID.
   451 	 * @return string Maybe-parsed widget ID.
   532 	 * @return string Maybe-parsed widget ID.
   452 	 */
   533 	 */
   453 	public function get_setting_id( $widget_id ) {
   534 	public function get_setting_id( $widget_id ) {
   459 		}
   540 		}
   460 		return $setting_id;
   541 		return $setting_id;
   461 	}
   542 	}
   462 
   543 
   463 	/**
   544 	/**
   464 	 * Determine whether the widget is considered "wide".
   545 	 * Determines whether the widget is considered "wide".
   465 	 *
   546 	 *
   466 	 * Core widgets which may have controls wider than 250, but can
   547 	 * Core widgets which may have controls wider than 250, but can still be shown
   467 	 * still be shown in the narrow Customizer panel. The RSS and Text
   548 	 * in the narrow Customizer panel. The RSS and Text widgets in Core, for example,
   468 	 * widgets in Core, for example, have widths of 400 and yet they
   549 	 * have widths of 400 and yet they still render fine in the Customizer panel.
   469 	 * still render fine in the Customizer panel. This method will
   550 	 *
   470 	 * return all Core widgets as being not wide, but this can be
   551 	 * This method will return all Core widgets as being not wide, but this can be
   471 	 * overridden with the is_wide_widget_in_customizer filter.
   552 	 * overridden with the {@see 'is_wide_widget_in_customizer'} filter.
   472 	 *
   553 	 *
   473 	 * @since 3.9.0
   554 	 * @since 3.9.0
   474 	 * @access public
   555 	 *
       
   556 	 * @global $wp_registered_widget_controls
   475 	 *
   557 	 *
   476 	 * @param string $widget_id Widget ID.
   558 	 * @param string $widget_id Widget ID.
   477 	 * @return bool Whether or not the widget is a "wide" widget.
   559 	 * @return bool Whether or not the widget is a "wide" widget.
   478 	 */
   560 	 */
   479 	public function is_wide_widget( $widget_id ) {
   561 	public function is_wide_widget( $widget_id ) {
   483 		$width            = $wp_registered_widget_controls[$widget_id]['width'];
   565 		$width            = $wp_registered_widget_controls[$widget_id]['width'];
   484 		$is_core          = in_array( $parsed_widget_id['id_base'], $this->core_widget_id_bases );
   566 		$is_core          = in_array( $parsed_widget_id['id_base'], $this->core_widget_id_bases );
   485 		$is_wide          = ( $width > 250 && ! $is_core );
   567 		$is_wide          = ( $width > 250 && ! $is_core );
   486 
   568 
   487 		/**
   569 		/**
   488 		 * Filter whether the given widget is considered "wide".
   570 		 * Filters whether the given widget is considered "wide".
   489 		 *
   571 		 *
   490 		 * @since 3.9.0
   572 		 * @since 3.9.0
   491 		 *
   573 		 *
   492 		 * @param bool   $is_wide   Whether the widget is wide, Default false.
   574 		 * @param bool   $is_wide   Whether the widget is wide, Default false.
   493 		 * @param string $widget_id Widget ID.
   575 		 * @param string $widget_id Widget ID.
   494 		 */
   576 		 */
   495 		return apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
   577 		return apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
   496 	}
   578 	}
   497 
   579 
   498 	/**
   580 	/**
   499 	 * Covert a widget ID into its id_base and number components.
   581 	 * Converts a widget ID into its id_base and number components.
   500 	 *
   582 	 *
   501 	 * @since 3.9.0
   583 	 * @since 3.9.0
   502 	 * @access public
       
   503 	 *
   584 	 *
   504 	 * @param string $widget_id Widget ID.
   585 	 * @param string $widget_id Widget ID.
   505 	 * @return array Array containing a widget's id_base and number components.
   586 	 * @return array Array containing a widget's id_base and number components.
   506 	 */
   587 	 */
   507 	public function parse_widget_id( $widget_id ) {
   588 	public function parse_widget_id( $widget_id ) {
   519 		}
   600 		}
   520 		return $parsed;
   601 		return $parsed;
   521 	}
   602 	}
   522 
   603 
   523 	/**
   604 	/**
   524 	 * Convert a widget setting ID (option path) to its id_base and number components.
   605 	 * Converts a widget setting ID (option path) to its id_base and number components.
   525 	 *
   606 	 *
   526 	 * @since 3.9.0
   607 	 * @since 3.9.0
   527 	 * @access public
       
   528 	 *
   608 	 *
   529 	 * @param string $setting_id Widget setting ID.
   609 	 * @param string $setting_id Widget setting ID.
   530 	 * @return WP_Error|array Array containing a widget's id_base and number components,
   610 	 * @return WP_Error|array Array containing a widget's id_base and number components,
   531 	 *                        or a WP_Error object.
   611 	 *                        or a WP_Error object.
   532 	 */
   612 	 */
   540 
   620 
   541 		return compact( 'id_base', 'number' );
   621 		return compact( 'id_base', 'number' );
   542 	}
   622 	}
   543 
   623 
   544 	/**
   624 	/**
   545 	 * Call admin_print_styles-widgets.php and admin_print_styles hooks to
   625 	 * Calls admin_print_styles-widgets.php and admin_print_styles hooks to
   546 	 * allow custom styles from plugins.
   626 	 * allow custom styles from plugins.
   547 	 *
   627 	 *
   548 	 * @since 3.9.0
   628 	 * @since 3.9.0
   549 	 * @access public
       
   550 	 */
   629 	 */
   551 	public function print_styles() {
   630 	public function print_styles() {
   552 		/** This action is documented in wp-admin/admin-header.php */
   631 		/** This action is documented in wp-admin/admin-header.php */
   553 		do_action( 'admin_print_styles-widgets.php' );
   632 		do_action( 'admin_print_styles-widgets.php' );
   554 
   633 
   555 		/** This action is documented in wp-admin/admin-header.php */
   634 		/** This action is documented in wp-admin/admin-header.php */
   556 		do_action( 'admin_print_styles' );
   635 		do_action( 'admin_print_styles' );
   557 	}
   636 	}
   558 
   637 
   559 	/**
   638 	/**
   560 	 * Call admin_print_scripts-widgets.php and admin_print_scripts hooks to
   639 	 * Calls admin_print_scripts-widgets.php and admin_print_scripts hooks to
   561 	 * allow custom scripts from plugins.
   640 	 * allow custom scripts from plugins.
   562 	 *
   641 	 *
   563 	 * @since 3.9.0
   642 	 * @since 3.9.0
   564 	 * @access public
       
   565 	 */
   643 	 */
   566 	public function print_scripts() {
   644 	public function print_scripts() {
   567 		/** This action is documented in wp-admin/admin-header.php */
   645 		/** This action is documented in wp-admin/admin-header.php */
   568 		do_action( 'admin_print_scripts-widgets.php' );
   646 		do_action( 'admin_print_scripts-widgets.php' );
   569 
   647 
   570 		/** This action is documented in wp-admin/admin-header.php */
   648 		/** This action is documented in wp-admin/admin-header.php */
   571 		do_action( 'admin_print_scripts' );
   649 		do_action( 'admin_print_scripts' );
   572 	}
   650 	}
   573 
   651 
   574 	/**
   652 	/**
   575 	 * Enqueue scripts and styles for Customizer panel and export data to JavaScript.
   653 	 * Enqueues scripts and styles for Customizer panel and export data to JavaScript.
   576 	 *
   654 	 *
   577 	 * @since 3.9.0
   655 	 * @since 3.9.0
   578 	 * @access public
   656 	 *
       
   657 	 * @global WP_Scripts $wp_scripts
       
   658 	 * @global array $wp_registered_sidebars
       
   659 	 * @global array $wp_registered_widgets
   579 	 */
   660 	 */
   580 	public function enqueue_scripts() {
   661 	public function enqueue_scripts() {
       
   662 		global $wp_scripts, $wp_registered_sidebars, $wp_registered_widgets;
       
   663 
   581 		wp_enqueue_style( 'customize-widgets' );
   664 		wp_enqueue_style( 'customize-widgets' );
   582 		wp_enqueue_script( 'customize-widgets' );
   665 		wp_enqueue_script( 'customize-widgets' );
   583 
   666 
   584 		/** This action is documented in wp-admin/admin-header.php */
   667 		/** This action is documented in wp-admin/admin-header.php */
   585 		do_action( 'admin_enqueue_scripts', 'widgets.php' );
   668 		do_action( 'admin_enqueue_scripts', 'widgets.php' );
   614 					<% _.each( sidebars, function ( sidebar ){ %>
   697 					<% _.each( sidebars, function ( sidebar ){ %>
   615 						<li class="" data-id="<%- sidebar.id %>" title="<%- sidebar.description %>" tabindex="0"><%- sidebar.name %></li>
   698 						<li class="" data-id="<%- sidebar.id %>" title="<%- sidebar.description %>" tabindex="0"><%- sidebar.name %></li>
   616 					<% }); %>
   699 					<% }); %>
   617 				</ul>
   700 				</ul>
   618 				<div class="move-widget-actions">
   701 				<div class="move-widget-actions">
   619 					<button class="move-widget-btn button-secondary" type="button">{btn}</button>
   702 					<button class="move-widget-btn button" type="button">{btn}</button>
   620 				</div>
   703 				</div>
   621 			</div>'
   704 			</div>'
   622 		);
   705 		);
   623 
   706 
   624 		global $wp_scripts;
   707 		/*
       
   708 		 * Gather all strings in PHP that may be needed by JS on the client.
       
   709 		 * Once JS i18n is implemented (in #20491), this can be removed.
       
   710 		 */
       
   711 		$some_non_rendered_areas_messages = array();
       
   712 		$some_non_rendered_areas_messages[1] = html_entity_decode(
       
   713 			__( 'Your theme has 1 other widget area, but this particular page doesn&#8217;t display it.' ),
       
   714 			ENT_QUOTES,
       
   715 			get_bloginfo( 'charset' )
       
   716 		);
       
   717 		$registered_sidebar_count = count( $wp_registered_sidebars );
       
   718 		for ( $non_rendered_count = 2; $non_rendered_count < $registered_sidebar_count; $non_rendered_count++ ) {
       
   719 			$some_non_rendered_areas_messages[ $non_rendered_count ] = html_entity_decode( sprintf(
       
   720 				/* translators: %s: the number of other widget areas registered but not rendered */
       
   721 				_n(
       
   722 					'Your theme has %s other widget area, but this particular page doesn&#8217;t display it.',
       
   723 					'Your theme has %s other widget areas, but this particular page doesn&#8217;t display them.',
       
   724 					$non_rendered_count
       
   725 				),
       
   726 				number_format_i18n( $non_rendered_count )
       
   727 			), ENT_QUOTES, get_bloginfo( 'charset' ) );
       
   728 		}
       
   729 
       
   730 		if ( 1 === $registered_sidebar_count ) {
       
   731 			$no_areas_shown_message = html_entity_decode( sprintf(
       
   732 				__( 'Your theme has 1 widget area, but this particular page doesn&#8217;t display it.' )
       
   733 			), ENT_QUOTES, get_bloginfo( 'charset' ) );
       
   734 		} else {
       
   735 			$no_areas_shown_message = html_entity_decode( sprintf(
       
   736 				/* translators: %s: the total number of widget areas registered */
       
   737 				_n(
       
   738 					'Your theme has %s widget area, but this particular page doesn&#8217;t display it.',
       
   739 					'Your theme has %s widget areas, but this particular page doesn&#8217;t display them.',
       
   740 					$registered_sidebar_count
       
   741 				),
       
   742 				number_format_i18n( $registered_sidebar_count )
       
   743 			), ENT_QUOTES, get_bloginfo( 'charset' ) );
       
   744 		}
   625 
   745 
   626 		$settings = array(
   746 		$settings = array(
   627 			'nonce'                => wp_create_nonce( 'update-widget' ),
   747 			'registeredSidebars'   => array_values( $wp_registered_sidebars ),
   628 			'registeredSidebars'   => array_values( $GLOBALS['wp_registered_sidebars'] ),
   748 			'registeredWidgets'    => $wp_registered_widgets,
   629 			'registeredWidgets'    => $GLOBALS['wp_registered_widgets'],
       
   630 			'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets
   749 			'availableWidgets'     => $available_widgets, // @todo Merge this with registered_widgets
   631 			'l10n' => array(
   750 			'l10n' => array(
   632 				'saveBtnLabel'     => __( 'Apply' ),
   751 				'saveBtnLabel'     => __( 'Apply' ),
   633 				'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ),
   752 				'saveBtnTooltip'   => __( 'Save and preview changes before publishing them.' ),
   634 				'removeBtnLabel'   => __( 'Remove' ),
   753 				'removeBtnLabel'   => __( 'Remove' ),
   635 				'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
   754 				'removeBtnTooltip' => __( 'Trash widget by moving it to the inactive widgets sidebar.' ),
   636 				'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
   755 				'error'            => __( 'An error has occurred. Please reload the page and try again.' ),
   637 				'widgetMovedUp'    => __( 'Widget moved up' ),
   756 				'widgetMovedUp'    => __( 'Widget moved up' ),
   638 				'widgetMovedDown'  => __( 'Widget moved down' ),
   757 				'widgetMovedDown'  => __( 'Widget moved down' ),
       
   758 				'navigatePreview'  => __( 'You can navigate to other pages on your site while using the Customizer to view and edit the widgets displayed on those pages.' ),
       
   759 				'someAreasShown'   => $some_non_rendered_areas_messages,
       
   760 				'noAreasShown'     => $no_areas_shown_message,
       
   761 				'reorderModeOn'    => __( 'Reorder mode enabled' ),
       
   762 				'reorderModeOff'   => __( 'Reorder mode closed' ),
       
   763 				'reorderLabelOn'   => esc_attr__( 'Reorder widgets' ),
       
   764 				/* translators: %d: the number of widgets found */
       
   765 				'widgetsFound'     => __( 'Number of widgets found: %d' ),
       
   766 				'noWidgetsFound'   => __( 'No widgets found.' ),
   639 			),
   767 			),
   640 			'tpl' => array(
   768 			'tpl' => array(
   641 				'widgetReorderNav' => $widget_reorder_nav_tpl,
   769 				'widgetReorderNav' => $widget_reorder_nav_tpl,
   642 				'moveWidgetArea'   => $move_widget_area_tpl,
   770 				'moveWidgetArea'   => $move_widget_area_tpl,
   643 			),
   771 			),
       
   772 			'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
   644 		);
   773 		);
   645 
   774 
   646 		foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
   775 		foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
   647 			unset( $registered_widget['callback'] ); // may not be JSON-serializeable
   776 			unset( $registered_widget['callback'] ); // may not be JSON-serializeable
   648 		}
   777 		}
   653 			sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
   782 			sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings ) )
   654 		);
   783 		);
   655 	}
   784 	}
   656 
   785 
   657 	/**
   786 	/**
   658 	 * Render the widget form control templates into the DOM.
   787 	 * Renders the widget form control templates into the DOM.
   659 	 *
   788 	 *
   660 	 * @since 3.9.0
   789 	 * @since 3.9.0
   661 	 * @access public
       
   662 	 */
   790 	 */
   663 	public function output_widget_control_templates() {
   791 	public function output_widget_control_templates() {
   664 		?>
   792 		?>
   665 		<div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
   793 		<div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
   666 		<div id="available-widgets">
   794 		<div id="available-widgets">
       
   795 			<div class="customize-section-title">
       
   796 				<button class="customize-section-back" tabindex="-1">
       
   797 					<span class="screen-reader-text"><?php _e( 'Back' ); ?></span>
       
   798 				</button>
       
   799 				<h3>
       
   800 					<span class="customize-action"><?php
       
   801 						/* translators: &#9656; is the unicode right-pointing triangle, and %s is the section title in the Customizer */
       
   802 						echo sprintf( __( 'Customizing &#9656; %s' ), esc_html( $this->manager->get_panel( 'widgets' )->title ) );
       
   803 					?></span>
       
   804 					<?php _e( 'Add a Widget' ); ?>
       
   805 				</h3>
       
   806 			</div>
   667 			<div id="available-widgets-filter">
   807 			<div id="available-widgets-filter">
   668 				<label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
   808 				<label class="screen-reader-text" for="widgets-search"><?php _e( 'Search Widgets' ); ?></label>
   669 				<input type="search" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" />
   809 				<input type="text" id="widgets-search" placeholder="<?php esc_attr_e( 'Search widgets&hellip;' ) ?>" aria-describedby="widgets-search-desc" />
       
   810 				<div class="search-icon" aria-hidden="true"></div>
       
   811 				<button type="button" class="clear-results"><span class="screen-reader-text"><?php _e( 'Clear Results' ); ?></span></button>
       
   812 				<p class="screen-reader-text" id="widgets-search-desc"><?php _e( 'The search results will be updated as you type.' ); ?></p>
   670 			</div>
   813 			</div>
   671 			<div id="available-widgets-list">
   814 			<div id="available-widgets-list">
   672 			<?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
   815 			<?php foreach ( $this->get_available_widgets() as $available_widget ): ?>
   673 				<div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">
   816 				<div id="widget-tpl-<?php echo esc_attr( $available_widget['id'] ) ?>" data-widget-id="<?php echo esc_attr( $available_widget['id'] ) ?>" class="widget-tpl <?php echo esc_attr( $available_widget['id'] ) ?>" tabindex="0">
   674 					<?php echo $available_widget['control_tpl']; ?>
   817 					<?php echo $available_widget['control_tpl']; ?>
   675 				</div>
   818 				</div>
   676 			<?php endforeach; ?>
   819 			<?php endforeach; ?>
       
   820 			<p class="no-widgets-found-message"><?php _e( 'No widgets found.' ); ?></p>
   677 			</div><!-- #available-widgets-list -->
   821 			</div><!-- #available-widgets-list -->
   678 		</div><!-- #available-widgets -->
   822 		</div><!-- #available-widgets -->
   679 		</div><!-- #widgets-left -->
   823 		</div><!-- #widgets-left -->
   680 		<?php
   824 		<?php
   681 	}
   825 	}
   682 
   826 
   683 	/**
   827 	/**
   684 	 * Call admin_print_footer_scripts and admin_print_scripts hooks to
   828 	 * Calls admin_print_footer_scripts and admin_print_scripts hooks to
   685 	 * allow custom scripts from plugins.
   829 	 * allow custom scripts from plugins.
   686 	 *
   830 	 *
   687 	 * @since 3.9.0
   831 	 * @since 3.9.0
   688 	 * @access public
       
   689 	 */
   832 	 */
   690 	public function print_footer_scripts() {
   833 	public function print_footer_scripts() {
   691 		/** This action is documented in wp-admin/admin-footer.php */
   834 		/** This action is documented in wp-admin/admin-footer.php */
       
   835 		do_action( 'admin_print_footer_scripts-widgets.php' );
       
   836 
       
   837 		/** This action is documented in wp-admin/admin-footer.php */
   692 		do_action( 'admin_print_footer_scripts' );
   838 		do_action( 'admin_print_footer_scripts' );
   693 
   839 
   694 		/** This action is documented in wp-admin/admin-footer.php */
   840 		/** This action is documented in wp-admin/admin-footer.php */
   695 		do_action( 'admin_footer-widgets.php' );
   841 		do_action( 'admin_footer-widgets.php' );
   696 	}
   842 	}
   697 
   843 
   698 	/**
   844 	/**
   699 	 * Get common arguments to supply when constructing a Customizer setting.
   845 	 * Retrieves common arguments to supply when constructing a Customizer setting.
   700 	 *
   846 	 *
   701 	 * @since 3.9.0
   847 	 * @since 3.9.0
   702 	 * @access public
       
   703 	 *
   848 	 *
   704 	 * @param string $id        Widget setting ID.
   849 	 * @param string $id        Widget setting ID.
   705 	 * @param array  $overrides Array of setting overrides.
   850 	 * @param array  $overrides Array of setting overrides.
   706 	 * @return array Possibly modified setting arguments.
   851 	 * @return array Possibly modified setting arguments.
   707 	 */
   852 	 */
   708 	public function get_setting_args( $id, $overrides = array() ) {
   853 	public function get_setting_args( $id, $overrides = array() ) {
   709 		$args = array(
   854 		$args = array(
   710 			'type'       => 'option',
   855 			'type'       => 'option',
   711 			'capability' => 'edit_theme_options',
   856 			'capability' => 'edit_theme_options',
   712 			'transport'  => 'refresh',
       
   713 			'default'    => array(),
   857 			'default'    => array(),
   714 		);
   858 		);
   715 
   859 
   716 		if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
   860 		if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
   717 			$args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
   861 			$args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
   718 			$args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
   862 			$args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
   719 		} else if ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
   863 			$args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets' ) ? 'postMessage' : 'refresh';
       
   864 		} elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
   720 			$args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
   865 			$args['sanitize_callback'] = array( $this, 'sanitize_widget_instance' );
   721 			$args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
   866 			$args['sanitize_js_callback'] = array( $this, 'sanitize_widget_js_instance' );
       
   867 			$args['transport'] = $this->is_widget_selective_refreshable( $matches['id_base'] ) ? 'postMessage' : 'refresh';
   722 		}
   868 		}
   723 
   869 
   724 		$args = array_merge( $args, $overrides );
   870 		$args = array_merge( $args, $overrides );
   725 
   871 
   726 		/**
   872 		/**
   727 		 * Filter the common arguments supplied when constructing a Customizer setting.
   873 		 * Filters the common arguments supplied when constructing a Customizer setting.
   728 		 *
   874 		 *
   729 		 * @since 3.9.0
   875 		 * @since 3.9.0
   730 		 *
   876 		 *
   731 		 * @see WP_Customize_Setting
   877 		 * @see WP_Customize_Setting
   732 		 *
   878 		 *
   735 		 */
   881 		 */
   736 		return apply_filters( 'widget_customizer_setting_args', $args, $id );
   882 		return apply_filters( 'widget_customizer_setting_args', $args, $id );
   737 	}
   883 	}
   738 
   884 
   739 	/**
   885 	/**
   740 	 * Make sure that sidebar widget arrays only ever contain widget IDS.
   886 	 * Ensures sidebar widget arrays only ever contain widget IDS.
   741 	 *
   887 	 *
   742 	 * Used as the 'sanitize_callback' for each $sidebars_widgets setting.
   888 	 * Used as the 'sanitize_callback' for each $sidebars_widgets setting.
   743 	 *
   889 	 *
   744 	 * @since 3.9.0
   890 	 * @since 3.9.0
   745 	 * @access public
       
   746 	 *
   891 	 *
   747 	 * @param array $widget_ids Array of widget IDs.
   892 	 * @param array $widget_ids Array of widget IDs.
   748 	 * @return array Array of sanitized widget IDs.
   893 	 * @return array Array of sanitized widget IDs.
   749 	 */
   894 	 */
   750 	public function sanitize_sidebar_widgets( $widget_ids ) {
   895 	public function sanitize_sidebar_widgets( $widget_ids ) {
   755 		}
   900 		}
   756 		return $sanitized_widget_ids;
   901 		return $sanitized_widget_ids;
   757 	}
   902 	}
   758 
   903 
   759 	/**
   904 	/**
   760 	 * Build up an index of all available widgets for use in Backbone models.
   905 	 * Builds up an index of all available widgets for use in Backbone models.
   761 	 *
   906 	 *
   762 	 * @since 3.9.0
   907 	 * @since 3.9.0
   763 	 * @access public
   908 	 *
       
   909 	 * @global array $wp_registered_widgets
       
   910 	 * @global array $wp_registered_widget_controls
       
   911 	 * @staticvar array $available_widgets
   764 	 *
   912 	 *
   765 	 * @see wp_list_widgets()
   913 	 * @see wp_list_widgets()
   766 	 *
   914 	 *
   767 	 * @return array List of available widgets.
   915 	 * @return array List of available widgets.
   768 	 */
   916 	 */
   825 				'is_multi'     => $is_multi_widget,
   973 				'is_multi'     => $is_multi_widget,
   826 				'control_tpl'  => $control_tpl,
   974 				'control_tpl'  => $control_tpl,
   827 				'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
   975 				'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
   828 				'is_disabled'  => $is_disabled,
   976 				'is_disabled'  => $is_disabled,
   829 				'id_base'      => $id_base,
   977 				'id_base'      => $id_base,
   830 				'transport'    => 'refresh',
   978 				'transport'    => $this->is_widget_selective_refreshable( $id_base ) ? 'postMessage' : 'refresh',
   831 				'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
   979 				'width'        => $wp_registered_widget_controls[$widget['id']]['width'],
   832 				'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
   980 				'height'       => $wp_registered_widget_controls[$widget['id']]['height'],
   833 				'is_wide'      => $this->is_wide_widget( $widget['id'] ),
   981 				'is_wide'      => $this->is_wide_widget( $widget['id'] ),
   834 			) );
   982 			) );
   835 
   983 
   838 
   986 
   839 		return $available_widgets;
   987 		return $available_widgets;
   840 	}
   988 	}
   841 
   989 
   842 	/**
   990 	/**
   843 	 * Naturally order available widgets by name.
   991 	 * Naturally orders available widgets by name.
   844 	 *
   992 	 *
   845 	 * @since 3.9.0
   993 	 * @since 3.9.0
   846 	 * @static
       
   847 	 * @access protected
       
   848 	 *
   994 	 *
   849 	 * @param array $widget_a The first widget to compare.
   995 	 * @param array $widget_a The first widget to compare.
   850 	 * @param array $widget_b The second widget to compare.
   996 	 * @param array $widget_b The second widget to compare.
   851 	 * @return int Reorder position for the current widget comparison.
   997 	 * @return int Reorder position for the current widget comparison.
   852 	 */
   998 	 */
   853 	protected function _sort_name_callback( $widget_a, $widget_b ) {
   999 	protected function _sort_name_callback( $widget_a, $widget_b ) {
   854 		return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
  1000 		return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
   855 	}
  1001 	}
   856 
  1002 
   857 	/**
  1003 	/**
   858 	 * Get the widget control markup.
  1004 	 * Retrieves the widget control markup.
   859 	 *
  1005 	 *
   860 	 * @since 3.9.0
  1006 	 * @since 3.9.0
   861 	 * @access public
       
   862 	 *
  1007 	 *
   863 	 * @param array $args Widget control arguments.
  1008 	 * @param array $args Widget control arguments.
   864 	 * @return string Widget control form HTML markup.
  1009 	 * @return string Widget control form HTML markup.
   865 	 */
  1010 	 */
   866 	public function get_widget_control( $args ) {
  1011 	public function get_widget_control( $args ) {
       
  1012 		$args[0]['before_form'] = '<div class="form">';
       
  1013 		$args[0]['after_form'] = '</div><!-- .form -->';
       
  1014 		$args[0]['before_widget_content'] = '<div class="widget-content">';
       
  1015 		$args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
   867 		ob_start();
  1016 		ob_start();
   868 
       
   869 		call_user_func_array( 'wp_widget_control', $args );
  1017 		call_user_func_array( 'wp_widget_control', $args );
   870 		$replacements = array(
       
   871 			'<form method="post">' => '<div class="form">',
       
   872 			'</form>' => '</div><!-- .form -->',
       
   873 		);
       
   874 
       
   875 		$control_tpl = ob_get_clean();
  1018 		$control_tpl = ob_get_clean();
   876 
       
   877 		$control_tpl = str_replace( array_keys( $replacements ), array_values( $replacements ), $control_tpl );
       
   878 
       
   879 		return $control_tpl;
  1019 		return $control_tpl;
   880 	}
  1020 	}
   881 
  1021 
   882 	/**
  1022 	/**
   883 	 * Add hooks for the Customizer preview.
  1023 	 * Retrieves the widget control markup parts.
   884 	 *
  1024 	 *
   885 	 * @since 3.9.0
  1025 	 * @since 4.4.0
   886 	 * @access public
  1026 	 *
       
  1027 	 * @param array $args Widget control arguments.
       
  1028 	 * @return array {
       
  1029 	 *     @type string $control Markup for widget control wrapping form.
       
  1030 	 *     @type string $content The contents of the widget form itself.
       
  1031 	 * }
       
  1032 	 */
       
  1033 	public function get_widget_control_parts( $args ) {
       
  1034 		$args[0]['before_widget_content'] = '<div class="widget-content">';
       
  1035 		$args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
       
  1036 		$control_markup = $this->get_widget_control( $args );
       
  1037 
       
  1038 		$content_start_pos = strpos( $control_markup, $args[0]['before_widget_content'] );
       
  1039 		$content_end_pos = strrpos( $control_markup, $args[0]['after_widget_content'] );
       
  1040 
       
  1041 		$control = substr( $control_markup, 0, $content_start_pos + strlen( $args[0]['before_widget_content'] ) );
       
  1042 		$control .= substr( $control_markup, $content_end_pos );
       
  1043 		$content = trim( substr(
       
  1044 			$control_markup,
       
  1045 			$content_start_pos + strlen( $args[0]['before_widget_content'] ),
       
  1046 			$content_end_pos - $content_start_pos - strlen( $args[0]['before_widget_content'] )
       
  1047 		) );
       
  1048 
       
  1049 		return compact( 'control', 'content' );
       
  1050 	}
       
  1051 
       
  1052 	/**
       
  1053 	 * Adds hooks for the Customizer preview.
       
  1054 	 *
       
  1055 	 * @since 3.9.0
   887 	 */
  1056 	 */
   888 	public function customize_preview_init() {
  1057 	public function customize_preview_init() {
   889 		add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
  1058 		add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
   890 		add_action( 'wp_print_styles',    array( $this, 'print_preview_css' ), 1 );
  1059 		add_action( 'wp_print_styles',    array( $this, 'print_preview_css' ), 1 );
   891 		add_action( 'wp_footer',          array( $this, 'export_preview_data' ), 20 );
  1060 		add_action( 'wp_footer',          array( $this, 'export_preview_data' ), 20 );
   892 	}
  1061 	}
   893 
  1062 
   894 	/**
  1063 	/**
   895 	 * Refresh nonce for widget updates.
  1064 	 * Refreshes the nonce for widget updates.
   896 	 *
  1065 	 *
   897 	 * @since 4.2.0
  1066 	 * @since 4.2.0
   898 	 * @access public
       
   899 	 *
  1067 	 *
   900 	 * @param  array $nonces Array of nonces.
  1068 	 * @param  array $nonces Array of nonces.
   901 	 * @return array $nonces Array of nonces.
  1069 	 * @return array $nonces Array of nonces.
   902 	 */
  1070 	 */
   903 	public function refresh_nonces( $nonces ) {
  1071 	public function refresh_nonces( $nonces ) {
   904 		$nonces['update-widget'] = wp_create_nonce( 'update-widget' );
  1072 		$nonces['update-widget'] = wp_create_nonce( 'update-widget' );
   905 		return $nonces;
  1073 		return $nonces;
   906 	}
  1074 	}
   907 
  1075 
   908 	/**
  1076 	/**
   909 	 * When previewing, make sure the proper previewing widgets are used.
  1077 	 * When previewing, ensures the proper previewing widgets are used.
   910 	 *
  1078 	 *
   911 	 * Because wp_get_sidebars_widgets() gets called early at init
  1079 	 * Because wp_get_sidebars_widgets() gets called early at {@see 'init' } (via
   912 	 * (via wp_convert_widget_settings()) and can set global variable
  1080 	 * wp_convert_widget_settings()) and can set global variable `$_wp_sidebars_widgets`
   913 	 * $_wp_sidebars_widgets to the value of get_option( 'sidebars_widgets' )
  1081 	 * to the value of `get_option( 'sidebars_widgets' )` before the Customizer preview
   914 	 * before the Customizer preview filter is added, we have to reset
  1082 	 * filter is added, it has to be reset after the filter has been added.
   915 	 * it after the filter has been added.
  1083 	 *
   916 	 *
  1084 	 * @since 3.9.0
   917 	 * @since 3.9.0
       
   918 	 * @access public
       
   919 	 *
  1085 	 *
   920 	 * @param array $sidebars_widgets List of widgets for the current sidebar.
  1086 	 * @param array $sidebars_widgets List of widgets for the current sidebar.
       
  1087 	 * @return array
   921 	 */
  1088 	 */
   922 	public function preview_sidebars_widgets( $sidebars_widgets ) {
  1089 	public function preview_sidebars_widgets( $sidebars_widgets ) {
   923 		$sidebars_widgets = get_option( 'sidebars_widgets' );
  1090 		$sidebars_widgets = get_option( 'sidebars_widgets', array() );
   924 
  1091 
   925 		unset( $sidebars_widgets['array_version'] );
  1092 		unset( $sidebars_widgets['array_version'] );
   926 		return $sidebars_widgets;
  1093 		return $sidebars_widgets;
   927 	}
  1094 	}
   928 
  1095 
   929 	/**
  1096 	/**
   930 	 * Enqueue scripts for the Customizer preview.
  1097 	 * Enqueues scripts for the Customizer preview.
   931 	 *
  1098 	 *
   932 	 * @since 3.9.0
  1099 	 * @since 3.9.0
   933 	 * @access public
       
   934 	 */
  1100 	 */
   935 	public function customize_preview_enqueue() {
  1101 	public function customize_preview_enqueue() {
   936 		wp_enqueue_script( 'customize-preview-widgets' );
  1102 		wp_enqueue_script( 'customize-preview-widgets' );
   937 	}
  1103 	}
   938 
  1104 
   939 	/**
  1105 	/**
   940 	 * Insert default style for highlighted widget at early point so theme
  1106 	 * Inserts default style for highlighted widget at early point so theme
   941 	 * stylesheet can override.
  1107 	 * stylesheet can override.
   942 	 *
  1108 	 *
   943 	 * @since 3.9.0
  1109 	 * @since 3.9.0
   944 	 * @access public
       
   945 	 *
       
   946 	 * @action wp_print_styles
       
   947 	 */
  1110 	 */
   948 	public function print_preview_css() {
  1111 	public function print_preview_css() {
   949 		?>
  1112 		?>
   950 		<style>
  1113 		<style>
   951 		.widget-customizer-highlighted-widget {
  1114 		.widget-customizer-highlighted-widget {
   958 		</style>
  1121 		</style>
   959 		<?php
  1122 		<?php
   960 	}
  1123 	}
   961 
  1124 
   962 	/**
  1125 	/**
   963 	 * At the very end of the page, at the very end of the wp_footer,
  1126 	 * Communicates the sidebars that appeared on the page at the very end of the page,
   964 	 * communicate the sidebars that appeared on the page.
  1127 	 * and at the very end of the wp_footer,
   965 	 *
  1128 	 *
   966 	 * @since 3.9.0
  1129 	 * @since 3.9.0
   967 	 * @access public
  1130      *
       
  1131 	 * @global array $wp_registered_sidebars
       
  1132 	 * @global array $wp_registered_widgets
   968 	 */
  1133 	 */
   969 	public function export_preview_data() {
  1134 	public function export_preview_data() {
       
  1135 		global $wp_registered_sidebars, $wp_registered_widgets;
       
  1136 
       
  1137 		$switched_locale = switch_to_locale( get_user_locale() );
       
  1138 		$l10n = array(
       
  1139 			'widgetTooltip'  => __( 'Shift-click to edit this widget.' ),
       
  1140 		);
       
  1141 		if ( $switched_locale ) {
       
  1142 			restore_previous_locale();
       
  1143 		}
   970 
  1144 
   971 		// Prepare Customizer settings to pass to JavaScript.
  1145 		// Prepare Customizer settings to pass to JavaScript.
   972 		$settings = array(
  1146 		$settings = array(
   973 			'renderedSidebars'   => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
  1147 			'renderedSidebars'   => array_fill_keys( array_unique( $this->rendered_sidebars ), true ),
   974 			'renderedWidgets'    => array_fill_keys( array_keys( $this->rendered_widgets ), true ),
  1148 			'renderedWidgets'    => array_fill_keys( array_keys( $this->rendered_widgets ), true ),
   975 			'registeredSidebars' => array_values( $GLOBALS['wp_registered_sidebars'] ),
  1149 			'registeredSidebars' => array_values( $wp_registered_sidebars ),
   976 			'registeredWidgets'  => $GLOBALS['wp_registered_widgets'],
  1150 			'registeredWidgets'  => $wp_registered_widgets,
   977 			'l10n'               => array(
  1151 			'l10n'               => $l10n,
   978 				'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
  1152 			'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
   979 			),
       
   980 		);
  1153 		);
   981 		foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
  1154 		foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
   982 			unset( $registered_widget['callback'] ); // may not be JSON-serializeable
  1155 			unset( $registered_widget['callback'] ); // may not be JSON-serializeable
   983 		}
  1156 		}
   984 
  1157 
   988 		</script>
  1161 		</script>
   989 		<?php
  1162 		<?php
   990 	}
  1163 	}
   991 
  1164 
   992 	/**
  1165 	/**
   993 	 * Keep track of the widgets that were rendered.
  1166 	 * Tracks the widgets that were rendered.
   994 	 *
  1167 	 *
   995 	 * @since 3.9.0
  1168 	 * @since 3.9.0
   996 	 * @access public
       
   997 	 *
  1169 	 *
   998 	 * @param array $widget Rendered widget to tally.
  1170 	 * @param array $widget Rendered widget to tally.
   999 	 */
  1171 	 */
  1000 	public function tally_rendered_widgets( $widget ) {
  1172 	public function tally_rendered_widgets( $widget ) {
  1001 		$this->rendered_widgets[ $widget['id'] ] = true;
  1173 		$this->rendered_widgets[ $widget['id'] ] = true;
  1003 
  1175 
  1004 	/**
  1176 	/**
  1005 	 * Determine if a widget is rendered on the page.
  1177 	 * Determine if a widget is rendered on the page.
  1006 	 *
  1178 	 *
  1007 	 * @since 4.0.0
  1179 	 * @since 4.0.0
  1008 	 * @access public
       
  1009 	 *
  1180 	 *
  1010 	 * @param string $widget_id Widget ID to check.
  1181 	 * @param string $widget_id Widget ID to check.
  1011 	 * @return bool Whether the widget is rendered.
  1182 	 * @return bool Whether the widget is rendered.
  1012 	 */
  1183 	 */
  1013 	public function is_widget_rendered( $widget_id ) {
  1184 	public function is_widget_rendered( $widget_id ) {
  1014 		return in_array( $widget_id, $this->rendered_widgets );
  1185 		return in_array( $widget_id, $this->rendered_widgets );
  1015 	}
  1186 	}
  1016 
  1187 
  1017 	/**
  1188 	/**
  1018 	 * Determine if a sidebar is rendered on the page.
  1189 	 * Determines if a sidebar is rendered on the page.
  1019 	 *
  1190 	 *
  1020 	 * @since 4.0.0
  1191 	 * @since 4.0.0
  1021 	 * @access public
       
  1022 	 *
  1192 	 *
  1023 	 * @param string $sidebar_id Sidebar ID to check.
  1193 	 * @param string $sidebar_id Sidebar ID to check.
  1024 	 * @return bool Whether the sidebar is rendered.
  1194 	 * @return bool Whether the sidebar is rendered.
  1025 	 */
  1195 	 */
  1026 	public function is_sidebar_rendered( $sidebar_id ) {
  1196 	public function is_sidebar_rendered( $sidebar_id ) {
  1027 		return in_array( $sidebar_id, $this->rendered_sidebars );
  1197 		return in_array( $sidebar_id, $this->rendered_sidebars );
  1028 	}
  1198 	}
  1029 
  1199 
  1030 	/**
  1200 	/**
  1031 	 * Tally the sidebars rendered via is_active_sidebar().
  1201 	 * Tallies the sidebars rendered via is_active_sidebar().
  1032 	 *
  1202 	 *
  1033 	 * Keep track of the times that is_active_sidebar() is called
  1203 	 * Keep track of the times that is_active_sidebar() is called in the template,
  1034 	 * in the template, and assume that this means that the sidebar
  1204 	 * and assume that this means that the sidebar would be rendered on the template
  1035 	 * would be rendered on the template if there were widgets
  1205 	 * if there were widgets populating it.
  1036 	 * populating it.
  1206 	 *
  1037 	 *
  1207 	 * @since 3.9.0
  1038 	 * @since 3.9.0
       
  1039 	 * @access public
       
  1040 	 *
  1208 	 *
  1041 	 * @param bool   $is_active  Whether the sidebar is active.
  1209 	 * @param bool   $is_active  Whether the sidebar is active.
  1042 	 * @param string $sidebar_id Sidebar ID.
  1210 	 * @param string $sidebar_id Sidebar ID.
       
  1211 	 * @return bool Whether the sidebar is active.
  1043 	 */
  1212 	 */
  1044 	public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
  1213 	public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
  1045 		if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
  1214 		if ( is_registered_sidebar( $sidebar_id ) ) {
  1046 			$this->rendered_sidebars[] = $sidebar_id;
  1215 			$this->rendered_sidebars[] = $sidebar_id;
  1047 		}
  1216 		}
  1048 		/*
  1217 		/*
  1049 		 * We may need to force this to true, and also force-true the value
  1218 		 * We may need to force this to true, and also force-true the value
  1050 		 * for 'dynamic_sidebar_has_widgets' if we want to ensure that there
  1219 		 * for 'dynamic_sidebar_has_widgets' if we want to ensure that there
  1052 		 */
  1221 		 */
  1053 		return $is_active;
  1222 		return $is_active;
  1054 	}
  1223 	}
  1055 
  1224 
  1056 	/**
  1225 	/**
  1057 	 * Tally the sidebars rendered via dynamic_sidebar().
  1226 	 * Tallies the sidebars rendered via dynamic_sidebar().
  1058 	 *
  1227 	 *
  1059 	 * Keep track of the times that dynamic_sidebar() is called in the template,
  1228 	 * Keep track of the times that dynamic_sidebar() is called in the template,
  1060 	 * and assume this means the sidebar would be rendered on the template if
  1229 	 * and assume this means the sidebar would be rendered on the template if
  1061 	 * there were widgets populating it.
  1230 	 * there were widgets populating it.
  1062 	 *
  1231 	 *
  1063 	 * @since 3.9.0
  1232 	 * @since 3.9.0
  1064 	 * @access public
       
  1065 	 *
  1233 	 *
  1066 	 * @param bool   $has_widgets Whether the current sidebar has widgets.
  1234 	 * @param bool   $has_widgets Whether the current sidebar has widgets.
  1067 	 * @param string $sidebar_id  Sidebar ID.
  1235 	 * @param string $sidebar_id  Sidebar ID.
       
  1236 	 * @return bool Whether the current sidebar has widgets.
  1068 	 */
  1237 	 */
  1069 	public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
  1238 	public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
  1070 		if ( isset( $GLOBALS['wp_registered_sidebars'][$sidebar_id] ) ) {
  1239 		if ( is_registered_sidebar( $sidebar_id ) ) {
  1071 			$this->rendered_sidebars[] = $sidebar_id;
  1240 			$this->rendered_sidebars[] = $sidebar_id;
  1072 		}
  1241 		}
  1073 
  1242 
  1074 		/*
  1243 		/*
  1075 		 * We may need to force this to true, and also force-true the value
  1244 		 * We may need to force this to true, and also force-true the value
  1078 		 */
  1247 		 */
  1079 		return $has_widgets;
  1248 		return $has_widgets;
  1080 	}
  1249 	}
  1081 
  1250 
  1082 	/**
  1251 	/**
  1083 	 * Get MAC for a serialized widget instance string.
  1252 	 * Retrieves MAC for a serialized widget instance string.
  1084 	 *
  1253 	 *
  1085 	 * Allows values posted back from JS to be rejected if any tampering of the
  1254 	 * Allows values posted back from JS to be rejected if any tampering of the
  1086 	 * data has occurred.
  1255 	 * data has occurred.
  1087 	 *
  1256 	 *
  1088 	 * @since 3.9.0
  1257 	 * @since 3.9.0
  1089 	 * @access protected
       
  1090 	 *
  1258 	 *
  1091 	 * @param string $serialized_instance Widget instance.
  1259 	 * @param string $serialized_instance Widget instance.
  1092 	 * @return string MAC for serialized widget instance.
  1260 	 * @return string MAC for serialized widget instance.
  1093 	 */
  1261 	 */
  1094 	protected function get_instance_hash_key( $serialized_instance ) {
  1262 	protected function get_instance_hash_key( $serialized_instance ) {
  1095 		return wp_hash( $serialized_instance );
  1263 		return wp_hash( $serialized_instance );
  1096 	}
  1264 	}
  1097 
  1265 
  1098 	/**
  1266 	/**
  1099 	 * Sanitize a widget instance.
  1267 	 * Sanitizes a widget instance.
  1100 	 *
  1268 	 *
  1101 	 * Unserialize the JS-instance for storing in the options. It's important
  1269 	 * Unserialize the JS-instance for storing in the options. It's important that this filter
  1102 	 * that this filter only get applied to an instance once.
  1270 	 * only get applied to an instance *once*.
  1103 	 *
  1271 	 *
  1104 	 * @since 3.9.0
  1272 	 * @since 3.9.0
  1105 	 * @access public
       
  1106 	 *
  1273 	 *
  1107 	 * @param array $value Widget instance to sanitize.
  1274 	 * @param array $value Widget instance to sanitize.
  1108 	 * @return array Sanitized widget instance.
  1275 	 * @return array|void Sanitized widget instance.
  1109 	 */
  1276 	 */
  1110 	public function sanitize_widget_instance( $value ) {
  1277 	public function sanitize_widget_instance( $value ) {
  1111 		if ( $value === array() ) {
  1278 		if ( $value === array() ) {
  1112 			return $value;
  1279 			return $value;
  1113 		}
  1280 		}
  1114 
  1281 
  1115 		if ( empty( $value['is_widget_customizer_js_value'] )
  1282 		if ( empty( $value['is_widget_customizer_js_value'] )
  1116 			|| empty( $value['instance_hash_key'] )
  1283 			|| empty( $value['instance_hash_key'] )
  1117 			|| empty( $value['encoded_serialized_instance'] ) )
  1284 			|| empty( $value['encoded_serialized_instance'] ) )
  1118 		{
  1285 		{
  1119 			return null;
  1286 			return;
  1120 		}
  1287 		}
  1121 
  1288 
  1122 		$decoded = base64_decode( $value['encoded_serialized_instance'], true );
  1289 		$decoded = base64_decode( $value['encoded_serialized_instance'], true );
  1123 		if ( false === $decoded ) {
  1290 		if ( false === $decoded ) {
  1124 			return null;
  1291 			return;
  1125 		}
  1292 		}
  1126 
  1293 
  1127 		if ( $this->get_instance_hash_key( $decoded ) !== $value['instance_hash_key'] ) {
  1294 		if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
  1128 			return null;
  1295 			return;
  1129 		}
  1296 		}
  1130 
  1297 
  1131 		$instance = unserialize( $decoded );
  1298 		$instance = unserialize( $decoded );
  1132 		if ( false === $instance ) {
  1299 		if ( false === $instance ) {
  1133 			return null;
  1300 			return;
  1134 		}
  1301 		}
  1135 
  1302 
  1136 		return $instance;
  1303 		return $instance;
  1137 	}
  1304 	}
  1138 
  1305 
  1139 	/**
  1306 	/**
  1140 	 * Convert widget instance into JSON-representable format.
  1307 	 * Converts a widget instance into JSON-representable format.
  1141 	 *
  1308 	 *
  1142 	 * @since 3.9.0
  1309 	 * @since 3.9.0
  1143 	 * @access public
       
  1144 	 *
  1310 	 *
  1145 	 * @param array $value Widget instance to convert to JSON.
  1311 	 * @param array $value Widget instance to convert to JSON.
  1146 	 * @return array JSON-converted widget instance.
  1312 	 * @return array JSON-converted widget instance.
  1147 	 */
  1313 	 */
  1148 	public function sanitize_widget_js_instance( $value ) {
  1314 	public function sanitize_widget_js_instance( $value ) {
  1158 		}
  1324 		}
  1159 		return $value;
  1325 		return $value;
  1160 	}
  1326 	}
  1161 
  1327 
  1162 	/**
  1328 	/**
  1163 	 * Strip out widget IDs for widgets which are no longer registered.
  1329 	 * Strips out widget IDs for widgets which are no longer registered.
  1164 	 *
  1330 	 *
  1165 	 * One example where this might happen is when a plugin orphans a widget
  1331 	 * One example where this might happen is when a plugin orphans a widget
  1166 	 * in a sidebar upon deactivation.
  1332 	 * in a sidebar upon deactivation.
  1167 	 *
  1333 	 *
  1168 	 * @since 3.9.0
  1334 	 * @since 3.9.0
  1169 	 * @access public
  1335 	 *
       
  1336 	 * @global array $wp_registered_widgets
  1170 	 *
  1337 	 *
  1171 	 * @param array $widget_ids List of widget IDs.
  1338 	 * @param array $widget_ids List of widget IDs.
  1172 	 * @return array Parsed list of widget IDs.
  1339 	 * @return array Parsed list of widget IDs.
  1173 	 */
  1340 	 */
  1174 	public function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
  1341 	public function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
  1176 		$widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
  1343 		$widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
  1177 		return $widget_ids;
  1344 		return $widget_ids;
  1178 	}
  1345 	}
  1179 
  1346 
  1180 	/**
  1347 	/**
  1181 	 * Find and invoke the widget update and control callbacks.
  1348 	 * Finds and invokes the widget update and control callbacks.
  1182 	 *
  1349 	 *
  1183 	 * Requires that $_POST be populated with the instance data.
  1350 	 * Requires that `$_POST` be populated with the instance data.
  1184 	 *
  1351 	 *
  1185 	 * @since 3.9.0
  1352 	 * @since 3.9.0
  1186 	 * @access public
  1353 	 *
       
  1354 	 * @global array $wp_registered_widget_updates
       
  1355 	 * @global array $wp_registered_widget_controls
  1187 	 *
  1356 	 *
  1188 	 * @param  string $widget_id Widget ID.
  1357 	 * @param  string $widget_id Widget ID.
  1189 	 * @return WP_Error|array Array containing the updated widget information.
  1358 	 * @return WP_Error|array Array containing the updated widget information.
  1190 	 *                        A WP_Error object, otherwise.
  1359 	 *                        A WP_Error object, otherwise.
  1191 	 */
  1360 	 */
  1192 	public function call_widget_update( $widget_id ) {
  1361 	public function call_widget_update( $widget_id ) {
  1193 		global $wp_registered_widget_updates, $wp_registered_widget_controls;
  1362 		global $wp_registered_widget_updates, $wp_registered_widget_controls;
       
  1363 
       
  1364 		$setting_id = $this->get_setting_id( $widget_id );
       
  1365 
       
  1366 		/*
       
  1367 		 * Make sure that other setting changes have previewed since this widget
       
  1368 		 * may depend on them (e.g. Menus being present for Navigation Menu widget).
       
  1369 		 */
       
  1370 		if ( ! did_action( 'customize_preview_init' ) ) {
       
  1371 			foreach ( $this->manager->settings() as $setting ) {
       
  1372 				if ( $setting->id !== $setting_id ) {
       
  1373 					$setting->preview();
       
  1374 				}
       
  1375 			}
       
  1376 		}
  1194 
  1377 
  1195 		$this->start_capturing_option_updates();
  1378 		$this->start_capturing_option_updates();
  1196 		$parsed_id   = $this->parse_widget_id( $widget_id );
  1379 		$parsed_id   = $this->parse_widget_id( $widget_id );
  1197 		$option_name = 'widget_' . $parsed_id['id_base'];
  1380 		$option_name = 'widget_' . $parsed_id['id_base'];
  1198 
  1381 
  1270 		 * Override the incoming $_POST['customized'] for a newly-created widget's
  1453 		 * Override the incoming $_POST['customized'] for a newly-created widget's
  1271 		 * setting with the new $instance so that the preview filter currently
  1454 		 * setting with the new $instance so that the preview filter currently
  1272 		 * in place from WP_Customize_Setting::preview() will use this value
  1455 		 * in place from WP_Customize_Setting::preview() will use this value
  1273 		 * instead of the default widget instance value (an empty array).
  1456 		 * instead of the default widget instance value (an empty array).
  1274 		 */
  1457 		 */
  1275 		$setting_id = $this->get_setting_id( $widget_id );
  1458 		$this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance ) );
  1276 		$this->manager->set_post_value( $setting_id, $instance );
       
  1277 
  1459 
  1278 		// Obtain the widget control with the updated instance in place.
  1460 		// Obtain the widget control with the updated instance in place.
  1279 		ob_start();
  1461 		ob_start();
  1280 		$form = $wp_registered_widget_controls[ $widget_id ];
  1462 		$form = $wp_registered_widget_controls[ $widget_id ];
  1281 		if ( $form ) {
  1463 		if ( $form ) {
  1287 
  1469 
  1288 		return compact( 'instance', 'form' );
  1470 		return compact( 'instance', 'form' );
  1289 	}
  1471 	}
  1290 
  1472 
  1291 	/**
  1473 	/**
  1292 	 * Update widget settings asynchronously.
  1474 	 * Updates widget settings asynchronously.
  1293 	 *
  1475 	 *
  1294 	 * Allows the Customizer to update a widget using its form, but return the new
  1476 	 * Allows the Customizer to update a widget using its form, but return the new
  1295 	 * instance info via Ajax instead of saving it to the options table.
  1477 	 * instance info via Ajax instead of saving it to the options table.
  1296 	 *
  1478 	 *
  1297 	 * Most code here copied from wp_ajax_save_widget()
  1479 	 * Most code here copied from wp_ajax_save_widget().
  1298 	 *
  1480 	 *
  1299 	 * @since 3.9.0
  1481 	 * @since 3.9.0
  1300 	 * @access public
       
  1301 	 *
  1482 	 *
  1302 	 * @see wp_ajax_save_widget()
  1483 	 * @see wp_ajax_save_widget()
  1303 	 *
       
  1304 	 */
  1484 	 */
  1305 	public function wp_ajax_update_widget() {
  1485 	public function wp_ajax_update_widget() {
  1306 
  1486 
  1307 		if ( ! is_user_logged_in() ) {
  1487 		if ( ! is_user_logged_in() ) {
  1308 			wp_die( 0 );
  1488 			wp_die( 0 );
  1342 			wp_send_json_error( 'template_widget_not_updatable' );
  1522 			wp_send_json_error( 'template_widget_not_updatable' );
  1343 		}
  1523 		}
  1344 
  1524 
  1345 		$updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
  1525 		$updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
  1346 		if ( is_wp_error( $updated_widget ) ) {
  1526 		if ( is_wp_error( $updated_widget ) ) {
  1347 			wp_send_json_error( $updated_widget->get_error_message() );
  1527 			wp_send_json_error( $updated_widget->get_error_code() );
  1348 		}
  1528 		}
  1349 
  1529 
  1350 		$form = $updated_widget['form'];
  1530 		$form = $updated_widget['form'];
  1351 		$instance = $this->sanitize_widget_js_instance( $updated_widget['instance'] );
  1531 		$instance = $this->sanitize_widget_js_instance( $updated_widget['instance'] );
  1352 
  1532 
  1353 		wp_send_json_success( compact( 'form', 'instance' ) );
  1533 		wp_send_json_success( compact( 'form', 'instance' ) );
  1354 	}
  1534 	}
  1355 
  1535 
  1356 	/***************************************************************************
  1536 	/*
  1357 	 * Option Update Capturing
  1537 	 * Selective Refresh Methods
  1358 	 ***************************************************************************/
  1538 	 */
       
  1539 
       
  1540 	/**
       
  1541 	 * Filters arguments for dynamic widget partials.
       
  1542 	 *
       
  1543 	 * @since 4.5.0
       
  1544 	 *
       
  1545 	 * @param array|false $partial_args Partial arguments.
       
  1546 	 * @param string      $partial_id   Partial ID.
       
  1547 	 * @return array (Maybe) modified partial arguments.
       
  1548 	 */
       
  1549 	public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
       
  1550 		if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
       
  1551 			return $partial_args;
       
  1552 		}
       
  1553 
       
  1554 		if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) {
       
  1555 			if ( false === $partial_args ) {
       
  1556 				$partial_args = array();
       
  1557 			}
       
  1558 			$partial_args = array_merge(
       
  1559 				$partial_args,
       
  1560 				array(
       
  1561 					'type'                => 'widget',
       
  1562 					'render_callback'     => array( $this, 'render_widget_partial' ),
       
  1563 					'container_inclusive' => true,
       
  1564 					'settings'            => array( $this->get_setting_id( $matches['widget_id'] ) ),
       
  1565 					'capability'          => 'edit_theme_options',
       
  1566 				)
       
  1567 			);
       
  1568 		}
       
  1569 
       
  1570 		return $partial_args;
       
  1571 	}
       
  1572 
       
  1573 	/**
       
  1574 	 * Adds hooks for selective refresh.
       
  1575 	 *
       
  1576 	 * @since 4.5.0
       
  1577 	 */
       
  1578 	public function selective_refresh_init() {
       
  1579 		if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
       
  1580 			return;
       
  1581 		}
       
  1582 		add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
       
  1583 		add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
       
  1584 		add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
       
  1585 		add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
       
  1586 	}
       
  1587 
       
  1588 	/**
       
  1589 	 * Inject selective refresh data attributes into widget container elements.
       
  1590 	 *
       
  1591 	 * @param array $params {
       
  1592 	 *     Dynamic sidebar params.
       
  1593 	 *
       
  1594 	 *     @type array $args        Sidebar args.
       
  1595 	 *     @type array $widget_args Widget args.
       
  1596 	 * }
       
  1597 	 * @see WP_Customize_Nav_Menus_Partial_Refresh::filter_wp_nav_menu_args()
       
  1598 	 *
       
  1599 	 * @return array Params.
       
  1600 	 */
       
  1601 	public function filter_dynamic_sidebar_params( $params ) {
       
  1602 		$sidebar_args = array_merge(
       
  1603 			array(
       
  1604 				'before_widget' => '',
       
  1605 				'after_widget' => '',
       
  1606 			),
       
  1607 			$params[0]
       
  1608 		);
       
  1609 
       
  1610 		// Skip widgets not in a registered sidebar or ones which lack a proper wrapper element to attach the data-* attributes to.
       
  1611 		$matches = array();
       
  1612 		$is_valid = (
       
  1613 			isset( $sidebar_args['id'] )
       
  1614 			&&
       
  1615 			is_registered_sidebar( $sidebar_args['id'] )
       
  1616 			&&
       
  1617 			( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
       
  1618 			&&
       
  1619 			preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
       
  1620 		);
       
  1621 		if ( ! $is_valid ) {
       
  1622 			return $params;
       
  1623 		}
       
  1624 		$this->before_widget_tags_seen[ $matches['tag_name'] ] = true;
       
  1625 
       
  1626 		$context = array(
       
  1627 			'sidebar_id' => $sidebar_args['id'],
       
  1628 		);
       
  1629 		if ( isset( $this->context_sidebar_instance_number ) ) {
       
  1630 			$context['sidebar_instance_number'] = $this->context_sidebar_instance_number;
       
  1631 		} else if ( isset( $sidebar_args['id'] ) && isset( $this->sidebar_instance_count[ $sidebar_args['id'] ] ) ) {
       
  1632 			$context['sidebar_instance_number'] = $this->sidebar_instance_count[ $sidebar_args['id'] ];
       
  1633 		}
       
  1634 
       
  1635 		$attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'widget[' . $sidebar_args['widget_id'] . ']' ) );
       
  1636 		$attributes .= ' data-customize-partial-type="widget"';
       
  1637 		$attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $context ) ) );
       
  1638 		$attributes .= sprintf( ' data-customize-widget-id="%s"', esc_attr( $sidebar_args['widget_id'] ) );
       
  1639 		$sidebar_args['before_widget'] = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $sidebar_args['before_widget'] );
       
  1640 
       
  1641 		$params[0] = $sidebar_args;
       
  1642 		return $params;
       
  1643 	}
       
  1644 
       
  1645 	/**
       
  1646 	 * List of the tag names seen for before_widget strings.
       
  1647 	 *
       
  1648 	 * This is used in the {@see 'filter_wp_kses_allowed_html'} filter to ensure that the
       
  1649 	 * data-* attributes can be whitelisted.
       
  1650 	 *
       
  1651 	 * @since 4.5.0
       
  1652 	 * @var array
       
  1653 	 */
       
  1654 	protected $before_widget_tags_seen = array();
       
  1655 
       
  1656 	/**
       
  1657 	 * Ensures the HTML data-* attributes for selective refresh are allowed by kses.
       
  1658 	 *
       
  1659 	 * This is needed in case the `$before_widget` is run through wp_kses() when printed.
       
  1660 	 *
       
  1661 	 * @since 4.5.0
       
  1662 	 *
       
  1663 	 * @param array $allowed_html Allowed HTML.
       
  1664 	 * @return array (Maybe) modified allowed HTML.
       
  1665 	 */
       
  1666 	public function filter_wp_kses_allowed_data_attributes( $allowed_html ) {
       
  1667 		foreach ( array_keys( $this->before_widget_tags_seen ) as $tag_name ) {
       
  1668 			if ( ! isset( $allowed_html[ $tag_name ] ) ) {
       
  1669 				$allowed_html[ $tag_name ] = array();
       
  1670 			}
       
  1671 			$allowed_html[ $tag_name ] = array_merge(
       
  1672 				$allowed_html[ $tag_name ],
       
  1673 				array_fill_keys( array(
       
  1674 					'data-customize-partial-id',
       
  1675 					'data-customize-partial-type',
       
  1676 					'data-customize-partial-placement-context',
       
  1677 					'data-customize-partial-widget-id',
       
  1678 					'data-customize-partial-options',
       
  1679 				), true )
       
  1680 			);
       
  1681 		}
       
  1682 		return $allowed_html;
       
  1683 	}
       
  1684 
       
  1685 	/**
       
  1686 	 * Keep track of the number of times that dynamic_sidebar() was called for a given sidebar index.
       
  1687 	 *
       
  1688 	 * This helps facilitate the uncommon scenario where a single sidebar is rendered multiple times on a template.
       
  1689 	 *
       
  1690 	 * @since 4.5.0
       
  1691 	 * @var array
       
  1692 	 */
       
  1693 	protected $sidebar_instance_count = array();
       
  1694 
       
  1695 	/**
       
  1696 	 * The current request's sidebar_instance_number context.
       
  1697 	 *
       
  1698 	 * @since 4.5.0
       
  1699 	 * @var int
       
  1700 	 */
       
  1701 	protected $context_sidebar_instance_number;
       
  1702 
       
  1703 	/**
       
  1704 	 * Current sidebar ID being rendered.
       
  1705 	 *
       
  1706 	 * @since 4.5.0
       
  1707 	 * @var array
       
  1708 	 */
       
  1709 	protected $current_dynamic_sidebar_id_stack = array();
       
  1710 
       
  1711 	/**
       
  1712 	 * Begins keeping track of the current sidebar being rendered.
       
  1713 	 *
       
  1714 	 * Insert marker before widgets are rendered in a dynamic sidebar.
       
  1715 	 *
       
  1716 	 * @since 4.5.0
       
  1717 	 *
       
  1718 	 * @param int|string $index Index, name, or ID of the dynamic sidebar.
       
  1719 	 */
       
  1720 	public function start_dynamic_sidebar( $index ) {
       
  1721 		array_unshift( $this->current_dynamic_sidebar_id_stack, $index );
       
  1722 		if ( ! isset( $this->sidebar_instance_count[ $index ] ) ) {
       
  1723 			$this->sidebar_instance_count[ $index ] = 0;
       
  1724 		}
       
  1725 		$this->sidebar_instance_count[ $index ] += 1;
       
  1726 		if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
       
  1727 			printf( "\n<!--dynamic_sidebar_before:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
       
  1728 		}
       
  1729 	}
       
  1730 
       
  1731 	/**
       
  1732 	 * Finishes keeping track of the current sidebar being rendered.
       
  1733 	 *
       
  1734 	 * Inserts a marker after widgets are rendered in a dynamic sidebar.
       
  1735 	 *
       
  1736 	 * @since 4.5.0
       
  1737 	 *
       
  1738 	 * @param int|string $index Index, name, or ID of the dynamic sidebar.
       
  1739 	 */
       
  1740 	public function end_dynamic_sidebar( $index ) {
       
  1741 		array_shift( $this->current_dynamic_sidebar_id_stack );
       
  1742 		if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
       
  1743 			printf( "\n<!--dynamic_sidebar_after:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
       
  1744 		}
       
  1745 	}
       
  1746 
       
  1747 	/**
       
  1748 	 * Current sidebar being rendered.
       
  1749 	 *
       
  1750 	 * @since 4.5.0
       
  1751 	 * @var string
       
  1752 	 */
       
  1753 	protected $rendering_widget_id;
       
  1754 
       
  1755 	/**
       
  1756 	 * Current widget being rendered.
       
  1757 	 *
       
  1758 	 * @since 4.5.0
       
  1759 	 * @var string
       
  1760 	 */
       
  1761 	protected $rendering_sidebar_id;
       
  1762 
       
  1763 	/**
       
  1764 	 * Filters sidebars_widgets to ensure the currently-rendered widget is the only widget in the current sidebar.
       
  1765 	 *
       
  1766 	 * @since 4.5.0
       
  1767 	 *
       
  1768 	 * @param array $sidebars_widgets Sidebars widgets.
       
  1769 	 * @return array Filtered sidebars widgets.
       
  1770 	 */
       
  1771 	public function filter_sidebars_widgets_for_rendering_widget( $sidebars_widgets ) {
       
  1772 		$sidebars_widgets[ $this->rendering_sidebar_id ] = array( $this->rendering_widget_id );
       
  1773 		return $sidebars_widgets;
       
  1774 	}
       
  1775 
       
  1776 	/**
       
  1777 	 * Renders a specific widget using the supplied sidebar arguments.
       
  1778 	 *
       
  1779 	 * @since 4.5.0
       
  1780 	 *
       
  1781 	 * @see dynamic_sidebar()
       
  1782 	 *
       
  1783 	 * @param WP_Customize_Partial $partial Partial.
       
  1784 	 * @param array                $context {
       
  1785 	 *     Sidebar args supplied as container context.
       
  1786 	 *
       
  1787 	 *     @type string $sidebar_id              ID for sidebar for widget to render into.
       
  1788 	 *     @type int    $sidebar_instance_number Disambiguating instance number.
       
  1789 	 * }
       
  1790 	 * @return string|false
       
  1791 	 */
       
  1792 	public function render_widget_partial( $partial, $context ) {
       
  1793 		$id_data   = $partial->id_data();
       
  1794 		$widget_id = array_shift( $id_data['keys'] );
       
  1795 
       
  1796 		if ( ! is_array( $context )
       
  1797 			|| empty( $context['sidebar_id'] )
       
  1798 			|| ! is_registered_sidebar( $context['sidebar_id'] )
       
  1799 		) {
       
  1800 			return false;
       
  1801 		}
       
  1802 
       
  1803 		$this->rendering_sidebar_id = $context['sidebar_id'];
       
  1804 
       
  1805 		if ( isset( $context['sidebar_instance_number'] ) ) {
       
  1806 			$this->context_sidebar_instance_number = intval( $context['sidebar_instance_number'] );
       
  1807 		}
       
  1808 
       
  1809 		// Filter sidebars_widgets so that only the queried widget is in the sidebar.
       
  1810 		$this->rendering_widget_id = $widget_id;
       
  1811 
       
  1812 		$filter_callback = array( $this, 'filter_sidebars_widgets_for_rendering_widget' );
       
  1813 		add_filter( 'sidebars_widgets', $filter_callback, 1000 );
       
  1814 
       
  1815 		// Render the widget.
       
  1816 		ob_start();
       
  1817 		dynamic_sidebar( $this->rendering_sidebar_id = $context['sidebar_id'] );
       
  1818 		$container = ob_get_clean();
       
  1819 
       
  1820 		// Reset variables for next partial render.
       
  1821 		remove_filter( 'sidebars_widgets', $filter_callback, 1000 );
       
  1822 
       
  1823 		$this->context_sidebar_instance_number = null;
       
  1824 		$this->rendering_sidebar_id = null;
       
  1825 		$this->rendering_widget_id = null;
       
  1826 
       
  1827 		return $container;
       
  1828 	}
       
  1829 
       
  1830 	//
       
  1831 	// Option Update Capturing
       
  1832 	//
  1359 
  1833 
  1360 	/**
  1834 	/**
  1361 	 * List of captured widget option updates.
  1835 	 * List of captured widget option updates.
  1362 	 *
  1836 	 *
  1363 	 * @since 3.9.0
  1837 	 * @since 3.9.0
  1364 	 * @access protected
       
  1365 	 * @var array $_captured_options Values updated while option capture is happening.
  1838 	 * @var array $_captured_options Values updated while option capture is happening.
  1366 	 */
  1839 	 */
  1367 	protected $_captured_options = array();
  1840 	protected $_captured_options = array();
  1368 
  1841 
  1369 	/**
  1842 	/**
  1370 	 * Whether option capture is currently happening.
  1843 	 * Whether option capture is currently happening.
  1371 	 *
  1844 	 *
  1372 	 * @since 3.9.0
  1845 	 * @since 3.9.0
  1373 	 * @access protected
       
  1374 	 * @var bool $_is_current Whether option capture is currently happening or not.
  1846 	 * @var bool $_is_current Whether option capture is currently happening or not.
  1375 	 */
  1847 	 */
  1376 	protected $_is_capturing_option_updates = false;
  1848 	protected $_is_capturing_option_updates = false;
  1377 
  1849 
  1378 	/**
  1850 	/**
  1379 	 * Determine whether the captured option update should be ignored.
  1851 	 * Determines whether the captured option update should be ignored.
  1380 	 *
  1852 	 *
  1381 	 * @since 3.9.0
  1853 	 * @since 3.9.0
  1382 	 * @access protected
       
  1383 	 *
  1854 	 *
  1384 	 * @param string $option_name Option name.
  1855 	 * @param string $option_name Option name.
  1385 	 * @return boolean Whether the option capture is ignored.
  1856 	 * @return bool Whether the option capture is ignored.
  1386 	 */
  1857 	 */
  1387 	protected function is_option_capture_ignored( $option_name ) {
  1858 	protected function is_option_capture_ignored( $option_name ) {
  1388 		return ( 0 === strpos( $option_name, '_transient_' ) );
  1859 		return ( 0 === strpos( $option_name, '_transient_' ) );
  1389 	}
  1860 	}
  1390 
  1861 
  1391 	/**
  1862 	/**
  1392 	 * Retrieve captured widget option updates.
  1863 	 * Retrieves captured widget option updates.
  1393 	 *
  1864 	 *
  1394 	 * @since 3.9.0
  1865 	 * @since 3.9.0
  1395 	 * @access protected
       
  1396 	 *
  1866 	 *
  1397 	 * @return array Array of captured options.
  1867 	 * @return array Array of captured options.
  1398 	 */
  1868 	 */
  1399 	protected function get_captured_options() {
  1869 	protected function get_captured_options() {
  1400 		return $this->_captured_options;
  1870 		return $this->_captured_options;
  1401 	}
  1871 	}
  1402 
  1872 
  1403 	/**
  1873 	/**
  1404 	 * Get the option that was captured from being saved.
  1874 	 * Retrieves the option that was captured from being saved.
  1405 	 *
  1875 	 *
  1406 	 * @since 4.2.0
  1876 	 * @since 4.2.0
  1407 	 * @access protected
       
  1408 	 *
  1877 	 *
  1409 	 * @param string $option_name Option name.
  1878 	 * @param string $option_name Option name.
  1410 	 * @param mixed  $default     Optional. Default value to return if the option does not exist.
  1879 	 * @param mixed  $default     Optional. Default value to return if the option does not exist. Default false.
  1411 	 * @return mixed Value set for the option.
  1880 	 * @return mixed Value set for the option.
  1412 	 */
  1881 	 */
  1413 	protected function get_captured_option( $option_name, $default = false ) {
  1882 	protected function get_captured_option( $option_name, $default = false ) {
  1414 		if ( array_key_exists( $option_name, $this->_captured_options ) ) {
  1883 		if ( array_key_exists( $option_name, $this->_captured_options ) ) {
  1415 			$value = $this->_captured_options[ $option_name ];
  1884 			$value = $this->_captured_options[ $option_name ];
  1418 		}
  1887 		}
  1419 		return $value;
  1888 		return $value;
  1420 	}
  1889 	}
  1421 
  1890 
  1422 	/**
  1891 	/**
  1423 	 * Get the number of captured widget option updates.
  1892 	 * Retrieves the number of captured widget option updates.
  1424 	 *
  1893 	 *
  1425 	 * @since 3.9.0
  1894 	 * @since 3.9.0
  1426 	 * @access protected
       
  1427 	 *
  1895 	 *
  1428 	 * @return int Number of updated options.
  1896 	 * @return int Number of updated options.
  1429 	 */
  1897 	 */
  1430 	protected function count_captured_options() {
  1898 	protected function count_captured_options() {
  1431 		return count( $this->_captured_options );
  1899 		return count( $this->_captured_options );
  1432 	}
  1900 	}
  1433 
  1901 
  1434 	/**
  1902 	/**
  1435 	 * Start keeping track of changes to widget options, caching new values.
  1903 	 * Begins keeping track of changes to widget options, caching new values.
  1436 	 *
  1904 	 *
  1437 	 * @since 3.9.0
  1905 	 * @since 3.9.0
  1438 	 * @access protected
       
  1439 	 */
  1906 	 */
  1440 	protected function start_capturing_option_updates() {
  1907 	protected function start_capturing_option_updates() {
  1441 		if ( $this->_is_capturing_option_updates ) {
  1908 		if ( $this->_is_capturing_option_updates ) {
  1442 			return;
  1909 			return;
  1443 		}
  1910 		}
  1446 
  1913 
  1447 		add_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
  1914 		add_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
  1448 	}
  1915 	}
  1449 
  1916 
  1450 	/**
  1917 	/**
  1451 	 * Pre-filter captured option values before updating.
  1918 	 * Pre-filters captured option values before updating.
  1452 	 *
  1919 	 *
  1453 	 * @since 3.9.0
  1920 	 * @since 3.9.0
  1454 	 * @access public
       
  1455 	 *
  1921 	 *
  1456 	 * @param mixed  $new_value   The new option value.
  1922 	 * @param mixed  $new_value   The new option value.
  1457 	 * @param string $option_name Name of the option.
  1923 	 * @param string $option_name Name of the option.
  1458 	 * @param mixed  $old_value   The old option value.
  1924 	 * @param mixed  $old_value   The old option value.
  1459 	 * @return mixed Filtered option value.
  1925 	 * @return mixed Filtered option value.
  1471 
  1937 
  1472 		return $old_value;
  1938 		return $old_value;
  1473 	}
  1939 	}
  1474 
  1940 
  1475 	/**
  1941 	/**
  1476 	 * Pre-filter captured option values before retrieving.
  1942 	 * Pre-filters captured option values before retrieving.
  1477 	 *
  1943 	 *
  1478 	 * @since 3.9.0
  1944 	 * @since 3.9.0
  1479 	 * @access public
       
  1480 	 *
  1945 	 *
  1481 	 * @param mixed $value Value to return instead of the option value.
  1946 	 * @param mixed $value Value to return instead of the option value.
  1482 	 * @return mixed Filtered option value.
  1947 	 * @return mixed Filtered option value.
  1483 	 */
  1948 	 */
  1484 	public function capture_filter_pre_get_option( $value ) {
  1949 	public function capture_filter_pre_get_option( $value ) {
  1493 
  1958 
  1494 		return $value;
  1959 		return $value;
  1495 	}
  1960 	}
  1496 
  1961 
  1497 	/**
  1962 	/**
  1498 	 * Undo any changes to the options since options capture began.
  1963 	 * Undoes any changes to the options since options capture began.
  1499 	 *
  1964 	 *
  1500 	 * @since 3.9.0
  1965 	 * @since 3.9.0
  1501 	 * @access protected
       
  1502 	 */
  1966 	 */
  1503 	protected function stop_capturing_option_updates() {
  1967 	protected function stop_capturing_option_updates() {
  1504 		if ( ! $this->_is_capturing_option_updates ) {
  1968 		if ( ! $this->_is_capturing_option_updates ) {
  1505 			return;
  1969 			return;
  1506 		}
  1970 		}
  1507 
  1971 
  1508 		remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
  1972 		remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10 );
  1509 
  1973 
  1510 		foreach ( array_keys( $this->_captured_options ) as $option_name ) {
  1974 		foreach ( array_keys( $this->_captured_options ) as $option_name ) {
  1511 			remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
  1975 			remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
  1512 		}
  1976 		}
  1513 
  1977 
  1514 		$this->_captured_options = array();
  1978 		$this->_captured_options = array();
  1515 		$this->_is_capturing_option_updates = false;
  1979 		$this->_is_capturing_option_updates = false;
  1516 	}
  1980 	}
  1517 
  1981 
  1518 	/**
  1982 	/**
  1519 	 * @since 3.9.0
  1983 	 * {@internal Missing Summary}
  1520 	 * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
  1984 	 *
       
  1985 	 * See the {@see 'customize_dynamic_setting_args'} filter.
       
  1986 	 *
       
  1987 	 * @since 3.9.0
       
  1988 	 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
  1521 	 */
  1989 	 */
  1522 	public function setup_widget_addition_previews() {
  1990 	public function setup_widget_addition_previews() {
  1523 		_deprecated_function( __METHOD__, '4.2.0' );
  1991 		_deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
  1524 	}
  1992 	}
  1525 
  1993 
  1526 	/**
  1994 	/**
  1527 	 * @since 3.9.0
  1995 	 * {@internal Missing Summary}
  1528 	 * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
  1996 	 *
       
  1997 	 * See the {@see 'customize_dynamic_setting_args'} filter.
       
  1998 	 *
       
  1999 	 * @since 3.9.0
       
  2000 	 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
  1529 	 */
  2001 	 */
  1530 	public function prepreview_added_sidebars_widgets() {
  2002 	public function prepreview_added_sidebars_widgets() {
  1531 		_deprecated_function( __METHOD__, '4.2.0' );
  2003 		_deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
  1532 	}
  2004 	}
  1533 
  2005 
  1534 	/**
  2006 	/**
  1535 	 * @since 3.9.0
  2007 	 * {@internal Missing Summary}
  1536 	 * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
  2008 	 *
       
  2009 	 * See the {@see 'customize_dynamic_setting_args'} filter.
       
  2010 	 *
       
  2011 	 * @since 3.9.0
       
  2012 	 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
  1537 	 */
  2013 	 */
  1538 	public function prepreview_added_widget_instance() {
  2014 	public function prepreview_added_widget_instance() {
  1539 		_deprecated_function( __METHOD__, '4.2.0' );
  2015 		_deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
  1540 	}
  2016 	}
  1541 
  2017 
  1542 	/**
  2018 	/**
  1543 	 * @since 3.9.0
  2019 	 * {@internal Missing Summary}
  1544 	 * @deprecated 4.2.0 Deprecated in favor of customize_dynamic_setting_args filter.
  2020 	 *
       
  2021 	 * See the {@see 'customize_dynamic_setting_args'} filter.
       
  2022 	 *
       
  2023 	 * @since 3.9.0
       
  2024 	 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
  1545 	 */
  2025 	 */
  1546 	public function remove_prepreview_filters() {
  2026 	public function remove_prepreview_filters() {
  1547 		_deprecated_function( __METHOD__, '4.2.0' );
  2027 		_deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
  1548 	}
  2028 	}
  1549 }
  2029 }