run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄class-wp-customize-widgets.php
1<?php
2/**
3 * WordPress Customize Widgets classes
4 *
5 * @package WordPress
6 * @subpackage Customize
7 * @since 3.9.0
8 */
9
10/**
11 * Customize Widgets class.
12 *
13 * Implements widget management in the Customizer.
14 *
15 * @since 3.9.0
16 *
17 * @see WP_Customize_Manager
18 */
19#[AllowDynamicProperties]
20final class WP_Customize_Widgets {
21
22 /**
23 * WP_Customize_Manager instance.
24 *
25 * @since 3.9.0
26 * @var WP_Customize_Manager
27 */
28 public $manager;
29
30 /**
31 * All id_bases for widgets defined in core.
32 *
33 * @since 3.9.0
34 * @var array
35 */
36 protected $core_widget_id_bases = array(
37 'archives',
38 'calendar',
39 'categories',
40 'custom_html',
41 'links',
42 'media_audio',
43 'media_image',
44 'media_video',
45 'meta',
46 'nav_menu',
47 'pages',
48 'recent-comments',
49 'recent-posts',
50 'rss',
51 'search',
52 'tag_cloud',
53 'text',
54 );
55
56 /**
57 * @since 3.9.0
58 * @var array
59 */
60 protected $rendered_sidebars = array();
61
62 /**
63 * @since 3.9.0
64 * @var array
65 */
66 protected $rendered_widgets = array();
67
68 /**
69 * @since 3.9.0
70 * @var array
71 */
72 protected $old_sidebars_widgets = array();
73
74 /**
75 * Mapping of widget ID base to whether it supports selective refresh.
76 *
77 * @since 4.5.0
78 * @var array
79 */
80 protected $selective_refreshable_widgets;
81
82 /**
83 * Mapping of setting type to setting ID pattern.
84 *
85 * @since 4.2.0
86 * @var array
87 */
88 protected $setting_id_patterns = array(
89 'widget_instance' => '/^widget_(?P<id_base>.+?)(?:\[(?P<widget_number>\d+)\])?$/',
90 'sidebar_widgets' => '/^sidebars_widgets\[(?P<sidebar_id>.+?)\]$/',
91 );
92
93 /**
94 * Initial loader.
95 *
96 * @since 3.9.0
97 *
98 * @param WP_Customize_Manager $manager Customizer bootstrap instance.
99 */
100 public function __construct( $manager ) {
101 $this->manager = $manager;
102
103 // See https://github.com/xwp/wp-customize-snapshots/blob/962586659688a5b1fd9ae93618b7ce2d4e7a421c/php/class-customize-snapshot-manager.php#L420-L449
104 add_filter( 'customize_dynamic_setting_args', array( $this, 'filter_customize_dynamic_setting_args' ), 10, 2 );
105 add_action( 'widgets_init', array( $this, 'register_settings' ), 95 );
106 add_action( 'customize_register', array( $this, 'schedule_customize_register' ), 1 );
107
108 // Skip remaining hooks when the user can't manage widgets anyway.
109 if ( ! current_user_can( 'edit_theme_options' ) ) {
110 return;
111 }
112
113 add_action( 'wp_loaded', array( $this, 'override_sidebars_widgets_for_theme_switch' ) );
114 add_action( 'customize_controls_init', array( $this, 'customize_controls_init' ) );
115 add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
116 add_action( 'customize_controls_print_styles', array( $this, 'print_styles' ) );
117 add_action( 'customize_controls_print_scripts', array( $this, 'print_scripts' ) );
118 add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_footer_scripts' ) );
119 add_action( 'customize_controls_print_footer_scripts', array( $this, 'output_widget_control_templates' ) );
120 add_action( 'customize_preview_init', array( $this, 'customize_preview_init' ) );
121 add_filter( 'customize_refresh_nonces', array( $this, 'refresh_nonces' ) );
122 add_filter( 'should_load_block_editor_scripts_and_styles', array( $this, 'should_load_block_editor_scripts_and_styles' ) );
123
124 add_action( 'dynamic_sidebar', array( $this, 'tally_rendered_widgets' ) );
125 add_filter( 'is_active_sidebar', array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
126 add_filter( 'dynamic_sidebar_has_widgets', array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
127
128 // Selective Refresh.
129 add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
130 add_action( 'customize_preview_init', array( $this, 'selective_refresh_init' ) );
131 }
132
133 /**
134 * List whether each registered widget can be use selective refresh.
135 *
136 * If the theme does not support the customize-selective-refresh-widgets feature,
137 * then this will always return an empty array.
138 *
139 * @since 4.5.0
140 *
141 * @global WP_Widget_Factory $wp_widget_factory
142 *
143 * @return array Mapping of id_base to support. If theme doesn't support
144 * selective refresh, an empty array is returned.
145 */
146 public function get_selective_refreshable_widgets() {
147 global $wp_widget_factory;
148 if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
149 return array();
150 }
151 if ( ! isset( $this->selective_refreshable_widgets ) ) {
152 $this->selective_refreshable_widgets = array();
153 foreach ( $wp_widget_factory->widgets as $wp_widget ) {
154 $this->selective_refreshable_widgets[ $wp_widget->id_base ] = ! empty( $wp_widget->widget_options['customize_selective_refresh'] );
155 }
156 }
157 return $this->selective_refreshable_widgets;
158 }
159
160 /**
161 * Determines if a widget supports selective refresh.
162 *
163 * @since 4.5.0
164 *
165 * @param string $id_base Widget ID Base.
166 * @return bool Whether the widget can be selective refreshed.
167 */
168 public function is_widget_selective_refreshable( $id_base ) {
169 $selective_refreshable_widgets = $this->get_selective_refreshable_widgets();
170 return ! empty( $selective_refreshable_widgets[ $id_base ] );
171 }
172
173 /**
174 * Retrieves the widget setting type given a setting ID.
175 *
176 * @since 4.2.0
177 *
178 * @param string $setting_id Setting ID.
179 * @return string|void Setting type.
180 */
181 protected function get_setting_type( $setting_id ) {
182 static $cache = array();
183 if ( isset( $cache[ $setting_id ] ) ) {
184 return $cache[ $setting_id ];
185 }
186 foreach ( $this->setting_id_patterns as $type => $pattern ) {
187 if ( preg_match( $pattern, $setting_id ) ) {
188 $cache[ $setting_id ] = $type;
189 return $type;
190 }
191 }
192 }
193
194 /**
195 * Inspects the incoming customized data for any widget settings, and dynamically adds
196 * them up-front so widgets will be initialized properly.
197 *
198 * @since 4.2.0
199 */
200 public function register_settings() {
201 $widget_setting_ids = array();
202 $incoming_setting_ids = array_keys( $this->manager->unsanitized_post_values() );
203 foreach ( $incoming_setting_ids as $setting_id ) {
204 if ( ! is_null( $this->get_setting_type( $setting_id ) ) ) {
205 $widget_setting_ids[] = $setting_id;
206 }
207 }
208 if ( $this->manager->doing_ajax( 'update-widget' ) && isset( $_REQUEST['widget-id'] ) ) {
209 $widget_setting_ids[] = $this->get_setting_id( wp_unslash( $_REQUEST['widget-id'] ) );
210 }
211
212 $settings = $this->manager->add_dynamic_settings( array_unique( $widget_setting_ids ) );
213
214 if ( $this->manager->settings_previewed() ) {
215 foreach ( $settings as $setting ) {
216 $setting->preview();
217 }
218 }
219 }
220
221 /**
222 * Determines the arguments for a dynamically-created setting.
223 *
224 * @since 4.2.0
225 *
226 * @param false|array $args The arguments to the WP_Customize_Setting constructor.
227 * @param string $setting_id ID for dynamic setting, usually coming from `$_POST['customized']`.
228 * @return array|false Setting arguments, false otherwise.
229 */
230 public function filter_customize_dynamic_setting_args( $args, $setting_id ) {
231 if ( $this->get_setting_type( $setting_id ) ) {
232 $args = $this->get_setting_args( $setting_id );
233 }
234 return $args;
235 }
236
237 /**
238 * Retrieves an unslashed post value or return a default.
239 *
240 * @since 3.9.0
241 *
242 * @param string $name Post value.
243 * @param mixed $default_value Default post value.
244 * @return mixed Unslashed post value or default value.
245 */
246 protected function get_post_value( $name, $default_value = null ) {
247 if ( ! isset( $_POST[ $name ] ) ) {
248 return $default_value;
249 }
250
251 return wp_unslash( $_POST[ $name ] );
252 }
253
254 /**
255 * Override sidebars_widgets for theme switch.
256 *
257 * When switching a theme via the Customizer, supply any previously-configured
258 * sidebars_widgets from the target theme as the initial sidebars_widgets
259 * setting. Also store the old theme's existing settings so that they can
260 * be passed along for storing in the sidebars_widgets theme_mod when the
261 * theme gets switched.
262 *
263 * @since 3.9.0
264 *
265 * @global array $sidebars_widgets
266 * @global array $_wp_sidebars_widgets
267 */
268 public function override_sidebars_widgets_for_theme_switch() {
269 global $sidebars_widgets;
270
271 if ( $this->manager->doing_ajax() || $this->manager->is_theme_active() ) {
272 return;
273 }
274
275 $this->old_sidebars_widgets = wp_get_sidebars_widgets();
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.
278
279 // retrieve_widgets() looks at the global $sidebars_widgets.
280 $sidebars_widgets = $this->old_sidebars_widgets;
281 $sidebars_widgets = retrieve_widgets( 'customize' );
282 add_filter( 'option_sidebars_widgets', array( $this, 'filter_option_sidebars_widgets_for_theme_switch' ), 1 );
283 // Reset global cache var used by wp_get_sidebars_widgets().
284 unset( $GLOBALS['_wp_sidebars_widgets'] );
285 }
286
287 /**
288 * Filters old_sidebars_widgets_data Customizer setting.
289 *
290 * When switching themes, filter the Customizer setting old_sidebars_widgets_data
291 * to supply initial $sidebars_widgets before they were overridden by retrieve_widgets().
292 * The value for old_sidebars_widgets_data gets set in the old theme's sidebars_widgets
293 * theme_mod.
294 *
295 * @since 3.9.0
296 *
297 * @see WP_Customize_Widgets::handle_theme_switch()
298 *
299 * @param array $old_sidebars_widgets
300 * @return array
301 */
302 public function filter_customize_value_old_sidebars_widgets_data( $old_sidebars_widgets ) {
303 return $this->old_sidebars_widgets;
304 }
305
306 /**
307 * Filters sidebars_widgets option for theme switch.
308 *
309 * When switching themes, the retrieve_widgets() function is run when the Customizer initializes,
310 * and then the new sidebars_widgets here get supplied as the default value for the sidebars_widgets
311 * option.
312 *
313 * @since 3.9.0
314 *
315 * @see WP_Customize_Widgets::handle_theme_switch()
316 * @global array $sidebars_widgets
317 *
318 * @param array $sidebars_widgets
319 * @return array
320 */
321 public function filter_option_sidebars_widgets_for_theme_switch( $sidebars_widgets ) {
322 $sidebars_widgets = $GLOBALS['sidebars_widgets'];
323 $sidebars_widgets['array_version'] = 3;
324 return $sidebars_widgets;
325 }
326
327 /**
328 * Ensures all widgets get loaded into the Customizer.
329 *
330 * Note: these actions are also fired in wp_ajax_update_widget().
331 *
332 * @since 3.9.0
333 */
334 public function customize_controls_init() {
335 /** This action is documented in wp-admin/includes/ajax-actions.php */
336 do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
337
338 /** This action is documented in wp-admin/includes/ajax-actions.php */
339 do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
340
341 /** This action is documented in wp-admin/widgets.php */
342 do_action( 'sidebar_admin_setup' );
343 }
344
345 /**
346 * Ensures widgets are available for all types of previews.
347 *
348 * When in preview, hook to {@see 'customize_register'} for settings after WordPress is loaded
349 * so that all filters have been initialized (e.g. Widget Visibility).
350 *
351 * @since 3.9.0
352 */
353 public function schedule_customize_register() {
354 if ( is_admin() ) {
355 $this->customize_register();
356 } else {
357 add_action( 'wp', array( $this, 'customize_register' ) );
358 }
359 }
360
361 /**
362 * Registers Customizer settings and controls for all sidebars and widgets.
363 *
364 * @since 3.9.0
365 *
366 * @global array $wp_registered_widgets
367 * @global array $wp_registered_widget_controls
368 * @global array $wp_registered_sidebars
369 */
370 public function customize_register() {
371 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_sidebars;
372
373 $use_widgets_block_editor = wp_use_widgets_block_editor();
374
375 add_filter( 'sidebars_widgets', array( $this, 'preview_sidebars_widgets' ), 1 );
376
377 $sidebars_widgets = array_merge(
378 array( 'wp_inactive_widgets' => array() ),
379 array_fill_keys( array_keys( $wp_registered_sidebars ), array() ),
380 wp_get_sidebars_widgets()
381 );
382
383 $new_setting_ids = array();
384
385 /*
386 * Register a setting for all widgets, including those which are active,
387 * inactive, and orphaned since a widget may get suppressed from a sidebar
388 * via a plugin (like Widget Visibility).
389 */
390 foreach ( array_keys( $wp_registered_widgets ) as $widget_id ) {
391 $setting_id = $this->get_setting_id( $widget_id );
392 $setting_args = $this->get_setting_args( $setting_id );
393 if ( ! $this->manager->get_setting( $setting_id ) ) {
394 $this->manager->add_setting( $setting_id, $setting_args );
395 }
396 $new_setting_ids[] = $setting_id;
397 }
398
399 /*
400 * Add a setting which will be supplied for the theme's sidebars_widgets
401 * theme_mod when the theme is switched.
402 */
403 if ( ! $this->manager->is_theme_active() ) {
404 $setting_id = 'old_sidebars_widgets_data';
405 $setting_args = $this->get_setting_args(
406 $setting_id,
407 array(
408 'type' => 'global_variable',
409 'dirty' => true,
410 )
411 );
412 $this->manager->add_setting( $setting_id, $setting_args );
413 }
414
415 $this->manager->add_panel(
416 'widgets',
417 array(
418 'type' => 'widgets',
419 'title' => __( 'Widgets' ),
420 'description' => __( 'Widgets are independent sections of content that can be placed into widgetized areas provided by your theme (commonly called sidebars).' ),
421 'priority' => 110,
422 'active_callback' => array( $this, 'is_panel_active' ),
423 'auto_expand_sole_section' => true,
424 'theme_supports' => 'widgets',
425 )
426 );
427
428 foreach ( $sidebars_widgets as $sidebar_id => $sidebar_widget_ids ) {
429 if ( empty( $sidebar_widget_ids ) ) {
430 $sidebar_widget_ids = array();
431 }
432
433 $is_registered_sidebar = is_registered_sidebar( $sidebar_id );
434 $is_inactive_widgets = ( 'wp_inactive_widgets' === $sidebar_id );
435 $is_active_sidebar = ( $is_registered_sidebar && ! $is_inactive_widgets );
436
437 // Add setting for managing the sidebar's widgets.
438 if ( $is_registered_sidebar || $is_inactive_widgets ) {
439 $setting_id = sprintf( 'sidebars_widgets[%s]', $sidebar_id );
440 $setting_args = $this->get_setting_args( $setting_id );
441 if ( ! $this->manager->get_setting( $setting_id ) ) {
442 if ( ! $this->manager->is_theme_active() ) {
443 $setting_args['dirty'] = true;
444 }
445 $this->manager->add_setting( $setting_id, $setting_args );
446 }
447 $new_setting_ids[] = $setting_id;
448
449 // Add section to contain controls.
450 $section_id = sprintf( 'sidebar-widgets-%s', $sidebar_id );
451 if ( $is_active_sidebar ) {
452
453 $section_args = array(
454 'title' => $wp_registered_sidebars[ $sidebar_id ]['name'],
455 'priority' => array_search( $sidebar_id, array_keys( $wp_registered_sidebars ), true ),
456 'panel' => 'widgets',
457 'sidebar_id' => $sidebar_id,
458 );
459
460 if ( $use_widgets_block_editor ) {
461 $section_args['description'] = '';
462 } else {
463 $section_args['description'] = $wp_registered_sidebars[ $sidebar_id ]['description'];
464 }
465
466 /**
467 * Filters Customizer widget section arguments for a given sidebar.
468 *
469 * @since 3.9.0
470 *
471 * @param array $section_args Array of Customizer widget section arguments.
472 * @param string $section_id Customizer section ID.
473 * @param int|string $sidebar_id Sidebar ID.
474 */
475 $section_args = apply_filters( 'customizer_widgets_section_args', $section_args, $section_id, $sidebar_id );
476
477 $section = new WP_Customize_Sidebar_Section( $this->manager, $section_id, $section_args );
478 $this->manager->add_section( $section );
479
480 if ( $use_widgets_block_editor ) {
481 $control = new WP_Sidebar_Block_Editor_Control(
482 $this->manager,
483 $setting_id,
484 array(
485 'section' => $section_id,
486 'sidebar_id' => $sidebar_id,
487 'label' => $section_args['title'],
488 'description' => $section_args['description'],
489 )
490 );
491 } else {
492 $control = new WP_Widget_Area_Customize_Control(
493 $this->manager,
494 $setting_id,
495 array(
496 'section' => $section_id,
497 'sidebar_id' => $sidebar_id,
498 'priority' => count( $sidebar_widget_ids ), // place 'Add Widget' and 'Reorder' buttons at end.
499 )
500 );
501 }
502
503 $this->manager->add_control( $control );
504
505 $new_setting_ids[] = $setting_id;
506 }
507 }
508
509 if ( ! $use_widgets_block_editor ) {
510 // Add a control for each active widget (located in a sidebar).
511 foreach ( $sidebar_widget_ids as $i => $widget_id ) {
512
513 // Skip widgets that may have gone away due to a plugin being deactivated.
514 if ( ! $is_active_sidebar || ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
515 continue;
516 }
517
518 $registered_widget = $wp_registered_widgets[ $widget_id ];
519 $setting_id = $this->get_setting_id( $widget_id );
520 $id_base = $wp_registered_widget_controls[ $widget_id ]['id_base'];
521
522 $control = new WP_Widget_Form_Customize_Control(
523 $this->manager,
524 $setting_id,
525 array(
526 'label' => $registered_widget['name'],
527 'section' => $section_id,
528 'sidebar_id' => $sidebar_id,
529 'widget_id' => $widget_id,
530 'widget_id_base' => $id_base,
531 'priority' => $i,
532 'width' => $wp_registered_widget_controls[ $widget_id ]['width'],
533 'height' => $wp_registered_widget_controls[ $widget_id ]['height'],
534 'is_wide' => $this->is_wide_widget( $widget_id ),
535 )
536 );
537 $this->manager->add_control( $control );
538 }
539 }
540 }
541
542 if ( $this->manager->settings_previewed() ) {
543 foreach ( $new_setting_ids as $new_setting_id ) {
544 $this->manager->get_setting( $new_setting_id )->preview();
545 }
546 }
547 }
548
549 /**
550 * Determines whether the widgets panel is active, based on whether there are sidebars registered.
551 *
552 * @since 4.4.0
553 *
554 * @see WP_Customize_Panel::$active_callback
555 *
556 * @global array $wp_registered_sidebars
557 *
558 * @return bool Active.
559 */
560 public function is_panel_active() {
561 global $wp_registered_sidebars;
562 return ! empty( $wp_registered_sidebars );
563 }
564
565 /**
566 * Converts a widget_id into its corresponding Customizer setting ID (option name).
567 *
568 * @since 3.9.0
569 *
570 * @param string $widget_id Widget ID.
571 * @return string Maybe-parsed widget ID.
572 */
573 public function get_setting_id( $widget_id ) {
574 $parsed_widget_id = $this->parse_widget_id( $widget_id );
575 $setting_id = sprintf( 'widget_%s', $parsed_widget_id['id_base'] );
576
577 if ( ! is_null( $parsed_widget_id['number'] ) ) {
578 $setting_id .= sprintf( '[%d]', $parsed_widget_id['number'] );
579 }
580 return $setting_id;
581 }
582
583 /**
584 * Determines whether the widget is considered "wide".
585 *
586 * Core widgets which may have controls wider than 250, but can still be shown
587 * in the narrow Customizer panel. The RSS and Text widgets in Core, for example,
588 * have widths of 400 and yet they still render fine in the Customizer panel.
589 *
590 * This method will return all Core widgets as being not wide, but this can be
591 * overridden with the {@see 'is_wide_widget_in_customizer'} filter.
592 *
593 * @since 3.9.0
594 *
595 * @global array $wp_registered_widget_controls
596 *
597 * @param string $widget_id Widget ID.
598 * @return bool Whether or not the widget is a "wide" widget.
599 */
600 public function is_wide_widget( $widget_id ) {
601 global $wp_registered_widget_controls;
602
603 $parsed_widget_id = $this->parse_widget_id( $widget_id );
604 $width = $wp_registered_widget_controls[ $widget_id ]['width'];
605 $is_core = in_array( $parsed_widget_id['id_base'], $this->core_widget_id_bases, true );
606 $is_wide = ( $width > 250 && ! $is_core );
607
608 /**
609 * Filters whether the given widget is considered "wide".
610 *
611 * @since 3.9.0
612 *
613 * @param bool $is_wide Whether the widget is wide, Default false.
614 * @param string $widget_id Widget ID.
615 */
616 return apply_filters( 'is_wide_widget_in_customizer', $is_wide, $widget_id );
617 }
618
619 /**
620 * Converts a widget ID into its id_base and number components.
621 *
622 * @since 3.9.0
623 *
624 * @param string $widget_id Widget ID.
625 * @return array Array containing a widget's id_base and number components.
626 */
627 public function parse_widget_id( $widget_id ) {
628 $parsed = array(
629 'number' => null,
630 'id_base' => null,
631 );
632
633 if ( preg_match( '/^(.+)-(\d+)$/', $widget_id, $matches ) ) {
634 $parsed['id_base'] = $matches[1];
635 $parsed['number'] = (int) $matches[2];
636 } else {
637 // Likely an old single widget.
638 $parsed['id_base'] = $widget_id;
639 }
640 return $parsed;
641 }
642
643 /**
644 * Converts a widget setting ID (option path) to its id_base and number components.
645 *
646 * @since 3.9.0
647 *
648 * @param string $setting_id Widget setting ID.
649 * @return array|WP_Error Array containing a widget's id_base and number components,
650 * or a WP_Error object.
651 */
652 public function parse_widget_setting_id( $setting_id ) {
653 if ( ! preg_match( '/^(widget_(.+?))(?:\[(\d+)\])?$/', $setting_id, $matches ) ) {
654 return new WP_Error( 'widget_setting_invalid_id' );
655 }
656
657 $id_base = $matches[2];
658 $number = isset( $matches[3] ) ? (int) $matches[3] : null;
659
660 return compact( 'id_base', 'number' );
661 }
662
663 /**
664 * Calls admin_print_styles-widgets.php and admin_print_styles hooks to
665 * allow custom styles from plugins.
666 *
667 * @since 3.9.0
668 */
669 public function print_styles() {
670 /** This action is documented in wp-admin/admin-header.php */
671 do_action( 'admin_print_styles-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
672
673 /** This action is documented in wp-admin/admin-header.php */
674 do_action( 'admin_print_styles' );
675 }
676
677 /**
678 * Calls admin_print_scripts-widgets.php and admin_print_scripts hooks to
679 * allow custom scripts from plugins.
680 *
681 * @since 3.9.0
682 */
683 public function print_scripts() {
684 /** This action is documented in wp-admin/admin-header.php */
685 do_action( 'admin_print_scripts-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
686
687 /** This action is documented in wp-admin/admin-header.php */
688 do_action( 'admin_print_scripts' );
689 }
690
691 /**
692 * Enqueues scripts and styles for Customizer panel and export data to JavaScript.
693 *
694 * @since 3.9.0
695 *
696 * @global WP_Scripts $wp_scripts
697 * @global array $wp_registered_sidebars
698 * @global array $wp_registered_widgets
699 */
700 public function enqueue_scripts() {
701 global $wp_scripts, $wp_registered_sidebars, $wp_registered_widgets;
702
703 wp_enqueue_style( 'customize-widgets' );
704 wp_enqueue_script( 'customize-widgets' );
705
706 /** This action is documented in wp-admin/admin-header.php */
707 do_action( 'admin_enqueue_scripts', 'widgets.php' );
708
709 /*
710 * Export available widgets with control_tpl removed from model
711 * since plugins need templates to be in the DOM.
712 */
713 $available_widgets = array();
714
715 foreach ( $this->get_available_widgets() as $available_widget ) {
716 unset( $available_widget['control_tpl'] );
717 $available_widgets[] = $available_widget;
718 }
719
720 $widget_reorder_nav_tpl = sprintf(
721 '<div class="widget-reorder-nav"><span class="move-widget" tabindex="0">%1$s</span><span class="move-widget-down" tabindex="0">%2$s</span><span class="move-widget-up" tabindex="0">%3$s</span></div>',
722 __( 'Move to another area&hellip;' ),
723 __( 'Move down' ),
724 __( 'Move up' )
725 );
726
727 $move_widget_area_tpl = str_replace(
728 array( '{description}', '{btn}' ),
729 array(
730 __( 'Select an area to move this widget into:' ),
731 _x( 'Move', 'Move widget' ),
732 ),
733 '<div class="move-widget-area">
734 <p class="description">{description}</p>
735 <ul class="widget-area-select">
736 <% _.each( sidebars, function ( sidebar ){ %>
737 <li class="" data-id="<%- sidebar.id %>" tabindex="0">
738 <div><strong><%- sidebar.name %></strong></div>
739 <div><%- sidebar.description %></div>
740 </li>
741 <% }); %>
742 </ul>
743 <div class="move-widget-actions">
744 <button class="move-widget-btn button" type="button">{btn}</button>
745 </div>
746 </div>'
747 );
748
749 /*
750 * Gather all strings in PHP that may be needed by JS on the client.
751 * Once JS i18n is implemented (in #20491), this can be removed.
752 */
753 $some_non_rendered_areas_messages = array();
754 $some_non_rendered_areas_messages[1] = html_entity_decode(
755 __( 'Your theme has 1 other widget area, but this particular page does not display it.' ),
756 ENT_QUOTES,
757 get_bloginfo( 'charset' )
758 );
759 $registered_sidebar_count = count( $wp_registered_sidebars );
760 for ( $non_rendered_count = 2; $non_rendered_count < $registered_sidebar_count; $non_rendered_count++ ) {
761 $some_non_rendered_areas_messages[ $non_rendered_count ] = html_entity_decode(
762 sprintf(
763 /* translators: %s: The number of other widget areas registered but not rendered. */
764 _n(
765 'Your theme has %s other widget area, but this particular page does not display it.',
766 'Your theme has %s other widget areas, but this particular page does not display them.',
767 $non_rendered_count
768 ),
769 number_format_i18n( $non_rendered_count )
770 ),
771 ENT_QUOTES,
772 get_bloginfo( 'charset' )
773 );
774 }
775
776 if ( 1 === $registered_sidebar_count ) {
777 $no_areas_shown_message = html_entity_decode(
778 sprintf(
779 __( 'Your theme has 1 widget area, but this particular page does not display it.' )
780 ),
781 ENT_QUOTES,
782 get_bloginfo( 'charset' )
783 );
784 } else {
785 $no_areas_shown_message = html_entity_decode(
786 sprintf(
787 /* translators: %s: The total number of widget areas registered. */
788 _n(
789 'Your theme has %s widget area, but this particular page does not display it.',
790 'Your theme has %s widget areas, but this particular page does not display them.',
791 $registered_sidebar_count
792 ),
793 number_format_i18n( $registered_sidebar_count )
794 ),
795 ENT_QUOTES,
796 get_bloginfo( 'charset' )
797 );
798 }
799
800 $settings = array(
801 'registeredSidebars' => array_values( $wp_registered_sidebars ),
802 'registeredWidgets' => $wp_registered_widgets,
803 'availableWidgets' => $available_widgets, // @todo Merge this with registered_widgets.
804 'l10n' => array(
805 'saveBtnLabel' => __( 'Apply' ),
806 'saveBtnTooltip' => __( 'Save and preview changes before publishing them.' ),
807 'removeBtnLabel' => __( 'Remove' ),
808 'removeBtnTooltip' => __( 'Keep widget settings and move it to the inactive widgets' ),
809 'error' => __( 'An error has occurred. Please reload the page and try again.' ),
810 'widgetMovedUp' => __( 'Widget moved up' ),
811 'widgetMovedDown' => __( 'Widget moved down' ),
812 'navigatePreview' => __( 'You can navigate to other pages on your site while using the Customizer to view and edit the widgets displayed on those pages.' ),
813 'someAreasShown' => $some_non_rendered_areas_messages,
814 'noAreasShown' => $no_areas_shown_message,
815 'reorderModeOn' => __( 'Reorder mode enabled' ),
816 'reorderModeOff' => __( 'Reorder mode closed' ),
817 'reorderLabelOn' => esc_attr__( 'Reorder widgets' ),
818 /* translators: %d: The number of widgets found. */
819 'widgetsFound' => __( 'Number of widgets found: %d' ),
820 'noWidgetsFound' => __( 'No widgets found.' ),
821 ),
822 'tpl' => array(
823 'widgetReorderNav' => $widget_reorder_nav_tpl,
824 'moveWidgetArea' => $move_widget_area_tpl,
825 ),
826 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
827 );
828
829 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
830 unset( $registered_widget['callback'] ); // May not be JSON-serializable.
831 }
832
833 $wp_scripts->add_data(
834 'customize-widgets',
835 'data',
836 sprintf( 'var _wpCustomizeWidgetsSettings = %s;', wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) )
837 );
838
839 /*
840 * TODO: Update 'wp-customize-widgets' to not rely so much on things in
841 * 'customize-widgets'. This will let us skip most of the above and not
842 * enqueue 'customize-widgets' which saves bytes.
843 */
844
845 if ( wp_use_widgets_block_editor() ) {
846 $block_editor_context = new WP_Block_Editor_Context(
847 array(
848 'name' => 'core/customize-widgets',
849 )
850 );
851
852 $editor_settings = get_block_editor_settings(
853 get_legacy_widget_block_editor_settings(),
854 $block_editor_context
855 );
856
857 wp_add_inline_script(
858 'wp-customize-widgets',
859 sprintf(
860 'wp.domReady( function() {
861 wp.customizeWidgets.initialize( "widgets-customizer", %s );
862 } );',
863 wp_json_encode( $editor_settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
864 )
865 );
866
867 // Preload server-registered block schemas.
868 wp_add_inline_script(
869 'wp-blocks',
870 'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings(), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ');'
871 );
872
873 // Preload server-registered block bindings sources.
874 $registered_sources = get_all_registered_block_bindings_sources();
875 if ( ! empty( $registered_sources ) ) {
876 $filtered_sources = array();
877 foreach ( $registered_sources as $source ) {
878 $filtered_sources[] = array(
879 'name' => $source->name,
880 'label' => $source->label,
881 'usesContext' => $source->uses_context,
882 );
883 }
884 $script = sprintf( 'for ( const source of %s ) { wp.blocks.registerBlockBindingsSource( source ); }', wp_json_encode( $filtered_sources, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) );
885 wp_add_inline_script(
886 'wp-blocks',
887 $script
888 );
889 }
890
891 wp_add_inline_script(
892 'wp-blocks',
893 sprintf( 'wp.blocks.setCategories( %s );', wp_json_encode( get_block_categories( $block_editor_context ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ),
894 'after'
895 );
896
897 wp_enqueue_script( 'wp-customize-widgets' );
898 wp_enqueue_style( 'wp-customize-widgets' );
899
900 /** This action is documented in edit-form-blocks.php */
901 do_action( 'enqueue_block_editor_assets' );
902 }
903 }
904
905 /**
906 * Renders the widget form control templates into the DOM.
907 *
908 * @since 3.9.0
909 */
910 public function output_widget_control_templates() {
911 ?>
912 <div id="widgets-left"><!-- compatibility with JS which looks for widget templates here -->
913 <div id="available-widgets">
914 <div class="customize-section-title">
915 <button class="customize-section-back" tabindex="-1">
916 <span class="screen-reader-text">
917 <?php
918 /* translators: Hidden accessibility text. */
919 _e( 'Back' );
920 ?>
921 </span>
922 </button>
923 <h3>
924 <span class="customize-action">
925 <?php
926 $panel = $this->manager->get_panel( 'widgets' );
927 $panel_title = isset( $panel->title ) ? $panel->title : __( 'Widgets' );
928 /* translators: &#9656; is the unicode right-pointing triangle. %s: Section title in the Customizer. */
929 printf( __( 'Customizing &#9656; %s' ), esc_html( $panel_title ) );
930 ?>
931 </span>
932 <?php _e( 'Add a Widget' ); ?>
933 </h3>
934 </div>
935 <div id="available-widgets-filter">
936 <label for="widgets-search">
937 <?php
938 /* translators: Hidden accessibility text. */
939 _e( 'Search Widgets' );
940 ?>
941 </label>
942 <input type="text" id="widgets-search" aria-describedby="widgets-search-desc" />
943 <div class="search-icon" aria-hidden="true"></div>
944 <button type="button" class="clear-results"><span class="screen-reader-text">
945 <?php
946 /* translators: Hidden accessibility text. */
947 _e( 'Clear Results' );
948 ?>
949 </span></button>
950 <p class="screen-reader-text" id="widgets-search-desc">
951 <?php
952 /* translators: Hidden accessibility text. */
953 _e( 'The search results will be updated as you type.' );
954 ?>
955 </p>
956 </div>
957 <div id="available-widgets-list">
958 <?php foreach ( $this->get_available_widgets() as $available_widget ) : ?>
959 <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">
960 <?php echo $available_widget['control_tpl']; ?>
961 </div>
962 <?php endforeach; ?>
963 <p class="no-widgets-found-message"><?php _e( 'No widgets found.' ); ?></p>
964 </div><!-- #available-widgets-list -->
965 </div><!-- #available-widgets -->
966 </div><!-- #widgets-left -->
967 <?php
968 }
969
970 /**
971 * Calls admin_print_footer_scripts and admin_print_scripts hooks to
972 * allow custom scripts from plugins.
973 *
974 * @since 3.9.0
975 */
976 public function print_footer_scripts() {
977 /** This action is documented in wp-admin/admin-footer.php */
978 do_action( 'admin_print_footer_scripts-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
979
980 /** This action is documented in wp-admin/admin-footer.php */
981 do_action( 'admin_print_footer_scripts' );
982
983 /** This action is documented in wp-admin/admin-footer.php */
984 do_action( 'admin_footer-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
985 }
986
987 /**
988 * Retrieves common arguments to supply when constructing a Customizer setting.
989 *
990 * @since 3.9.0
991 *
992 * @param string $id Widget setting ID.
993 * @param array $overrides Array of setting overrides.
994 * @return array Possibly modified setting arguments.
995 */
996 public function get_setting_args( $id, $overrides = array() ) {
997 $args = array(
998 'type' => 'option',
999 'capability' => 'edit_theme_options',
1000 'default' => array(),
1001 );
1002
1003 if ( preg_match( $this->setting_id_patterns['sidebar_widgets'], $id, $matches ) ) {
1004 $args['sanitize_callback'] = array( $this, 'sanitize_sidebar_widgets' );
1005 $args['sanitize_js_callback'] = array( $this, 'sanitize_sidebar_widgets_js_instance' );
1006 $args['transport'] = current_theme_supports( 'customize-selective-refresh-widgets' ) ? 'postMessage' : 'refresh';
1007 } elseif ( preg_match( $this->setting_id_patterns['widget_instance'], $id, $matches ) ) {
1008 $id_base = $matches['id_base'];
1009 $args['sanitize_callback'] = function ( $value ) use ( $id_base ) {
1010 return $this->sanitize_widget_instance( $value, $id_base );
1011 };
1012 $args['sanitize_js_callback'] = function ( $value ) use ( $id_base ) {
1013 return $this->sanitize_widget_js_instance( $value, $id_base );
1014 };
1015 $args['transport'] = $this->is_widget_selective_refreshable( $matches['id_base'] ) ? 'postMessage' : 'refresh';
1016 }
1017
1018 $args = array_merge( $args, $overrides );
1019
1020 /**
1021 * Filters the common arguments supplied when constructing a Customizer setting.
1022 *
1023 * @since 3.9.0
1024 *
1025 * @see WP_Customize_Setting
1026 *
1027 * @param array $args Array of Customizer setting arguments.
1028 * @param string $id Widget setting ID.
1029 */
1030 return apply_filters( 'widget_customizer_setting_args', $args, $id );
1031 }
1032
1033 /**
1034 * Ensures sidebar widget arrays only ever contain widget IDS.
1035 *
1036 * Used as the 'sanitize_callback' for each $sidebars_widgets setting.
1037 *
1038 * @since 3.9.0
1039 *
1040 * @param string[] $widget_ids Array of widget IDs.
1041 * @return string[] Array of sanitized widget IDs.
1042 */
1043 public function sanitize_sidebar_widgets( $widget_ids ) {
1044 $widget_ids = array_map( 'strval', (array) $widget_ids );
1045 $sanitized_widget_ids = array();
1046 foreach ( $widget_ids as $widget_id ) {
1047 $sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
1048 }
1049 return $sanitized_widget_ids;
1050 }
1051
1052 /**
1053 * Builds up an index of all available widgets for use in Backbone models.
1054 *
1055 * @since 3.9.0
1056 *
1057 * @global array $wp_registered_widgets
1058 * @global array $wp_registered_widget_controls
1059 *
1060 * @see wp_list_widgets()
1061 *
1062 * @return array List of available widgets.
1063 */
1064 public function get_available_widgets() {
1065 static $available_widgets = array();
1066 if ( ! empty( $available_widgets ) ) {
1067 return $available_widgets;
1068 }
1069
1070 global $wp_registered_widgets, $wp_registered_widget_controls;
1071 require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
1072
1073 $sort = $wp_registered_widgets;
1074 usort( $sort, array( $this, '_sort_name_callback' ) );
1075 $done = array();
1076
1077 foreach ( $sort as $widget ) {
1078 if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget.
1079 continue;
1080 }
1081
1082 $sidebar = is_active_widget( $widget['callback'], $widget['id'], false, false );
1083 $done[] = $widget['callback'];
1084
1085 if ( ! isset( $widget['params'][0] ) ) {
1086 $widget['params'][0] = array();
1087 }
1088
1089 $available_widget = $widget;
1090 unset( $available_widget['callback'] ); // Not serializable to JSON.
1091
1092 $args = array(
1093 'widget_id' => $widget['id'],
1094 'widget_name' => $widget['name'],
1095 '_display' => 'template',
1096 );
1097
1098 $is_disabled = false;
1099 $is_multi_widget = ( isset( $wp_registered_widget_controls[ $widget['id'] ]['id_base'] ) && isset( $widget['params'][0]['number'] ) );
1100 if ( $is_multi_widget ) {
1101 $id_base = $wp_registered_widget_controls[ $widget['id'] ]['id_base'];
1102 $args['_temp_id'] = "$id_base-__i__";
1103 $args['_multi_num'] = next_widget_id_number( $id_base );
1104 $args['_add'] = 'multi';
1105 } else {
1106 $args['_add'] = 'single';
1107
1108 if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) {
1109 $is_disabled = true;
1110 }
1111 $id_base = $widget['id'];
1112 }
1113
1114 $list_widget_controls_args = wp_list_widget_controls_dynamic_sidebar(
1115 array(
1116 0 => $args,
1117 1 => $widget['params'][0],
1118 )
1119 );
1120 $control_tpl = $this->get_widget_control( $list_widget_controls_args );
1121
1122 // The properties here are mapped to the Backbone Widget model.
1123 $available_widget = array_merge(
1124 $available_widget,
1125 array(
1126 'temp_id' => isset( $args['_temp_id'] ) ? $args['_temp_id'] : null,
1127 'is_multi' => $is_multi_widget,
1128 'control_tpl' => $control_tpl,
1129 'multi_number' => ( 'multi' === $args['_add'] ) ? $args['_multi_num'] : false,
1130 'is_disabled' => $is_disabled,
1131 'id_base' => $id_base,
1132 'transport' => $this->is_widget_selective_refreshable( $id_base ) ? 'postMessage' : 'refresh',
1133 'width' => $wp_registered_widget_controls[ $widget['id'] ]['width'],
1134 'height' => $wp_registered_widget_controls[ $widget['id'] ]['height'],
1135 'is_wide' => $this->is_wide_widget( $widget['id'] ),
1136 )
1137 );
1138
1139 $available_widgets[] = $available_widget;
1140 }
1141
1142 return $available_widgets;
1143 }
1144
1145 /**
1146 * Naturally orders available widgets by name.
1147 *
1148 * @since 3.9.0
1149 *
1150 * @param array $widget_a The first widget to compare.
1151 * @param array $widget_b The second widget to compare.
1152 * @return int Reorder position for the current widget comparison.
1153 */
1154 protected function _sort_name_callback( $widget_a, $widget_b ) {
1155 return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
1156 }
1157
1158 /**
1159 * Retrieves the widget control markup.
1160 *
1161 * @since 3.9.0
1162 *
1163 * @param array $args Widget control arguments.
1164 * @return string Widget control form HTML markup.
1165 */
1166 public function get_widget_control( $args ) {
1167 $args[0]['before_form'] = '<div class="form">';
1168 $args[0]['after_form'] = '</div><!-- .form -->';
1169 $args[0]['before_widget_content'] = '<div class="widget-content">';
1170 $args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
1171 ob_start();
1172 wp_widget_control( ...$args );
1173 $control_tpl = ob_get_clean();
1174 return $control_tpl;
1175 }
1176
1177 /**
1178 * Retrieves the widget control markup parts.
1179 *
1180 * @since 4.4.0
1181 *
1182 * @param array $args Widget control arguments.
1183 * @return array {
1184 * @type string $control Markup for widget control wrapping form.
1185 * @type string $content The contents of the widget form itself.
1186 * }
1187 */
1188 public function get_widget_control_parts( $args ) {
1189 $args[0]['before_widget_content'] = '<div class="widget-content">';
1190 $args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
1191 $control_markup = $this->get_widget_control( $args );
1192
1193 $content_start_pos = strpos( $control_markup, $args[0]['before_widget_content'] );
1194 $content_end_pos = strrpos( $control_markup, $args[0]['after_widget_content'] );
1195
1196 $control = substr( $control_markup, 0, $content_start_pos + strlen( $args[0]['before_widget_content'] ) );
1197 $control .= substr( $control_markup, $content_end_pos );
1198 $content = trim(
1199 substr(
1200 $control_markup,
1201 $content_start_pos + strlen( $args[0]['before_widget_content'] ),
1202 $content_end_pos - $content_start_pos - strlen( $args[0]['before_widget_content'] )
1203 )
1204 );
1205
1206 return compact( 'control', 'content' );
1207 }
1208
1209 /**
1210 * Adds hooks for the Customizer preview.
1211 *
1212 * @since 3.9.0
1213 */
1214 public function customize_preview_init() {
1215 add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
1216 add_action( 'wp_print_styles', array( $this, 'print_preview_css' ), 1 );
1217 add_action( 'wp_footer', array( $this, 'export_preview_data' ), 20 );
1218 }
1219
1220 /**
1221 * Refreshes the nonce for widget updates.
1222 *
1223 * @since 4.2.0
1224 *
1225 * @param array $nonces Array of nonces.
1226 * @return array Array of nonces.
1227 */
1228 public function refresh_nonces( $nonces ) {
1229 $nonces['update-widget'] = wp_create_nonce( 'update-widget' );
1230 return $nonces;
1231 }
1232
1233 /**
1234 * Tells the script loader to load the scripts and styles of custom blocks
1235 * if the widgets block editor is enabled.
1236 *
1237 * @since 5.8.0
1238 *
1239 * @param bool $is_block_editor_screen Current decision about loading block assets.
1240 * @return bool Filtered decision about loading block assets.
1241 */
1242 public function should_load_block_editor_scripts_and_styles( $is_block_editor_screen ) {
1243 if ( wp_use_widgets_block_editor() ) {
1244 return true;
1245 }
1246
1247 return $is_block_editor_screen;
1248 }
1249
1250 /**
1251 * When previewing, ensures the proper previewing widgets are used.
1252 *
1253 * Because wp_get_sidebars_widgets() gets called early at {@see 'init' } (via
1254 * wp_convert_widget_settings()) and can set global variable `$_wp_sidebars_widgets`
1255 * to the value of `get_option( 'sidebars_widgets' )` before the Customizer preview
1256 * filter is added, it has to be reset after the filter has been added.
1257 *
1258 * @since 3.9.0
1259 *
1260 * @param array $sidebars_widgets List of widgets for the current sidebar.
1261 * @return array
1262 */
1263 public function preview_sidebars_widgets( $sidebars_widgets ) {
1264 $sidebars_widgets = get_option( 'sidebars_widgets', array() );
1265
1266 unset( $sidebars_widgets['array_version'] );
1267 return $sidebars_widgets;
1268 }
1269
1270 /**
1271 * Enqueues scripts for the Customizer preview.
1272 *
1273 * @since 3.9.0
1274 */
1275 public function customize_preview_enqueue() {
1276 wp_enqueue_script( 'customize-preview-widgets' );
1277 }
1278
1279 /**
1280 * Inserts default style for highlighted widget at early point so theme
1281 * stylesheet can override.
1282 *
1283 * @since 3.9.0
1284 */
1285 public function print_preview_css() {
1286 ?>
1287 <style>
1288 .widget-customizer-highlighted-widget {
1289 outline: none;
1290 -webkit-box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
1291 box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
1292 position: relative;
1293 z-index: 1;
1294 }
1295 </style>
1296 <?php
1297 }
1298
1299 /**
1300 * Communicates the sidebars that appeared on the page at the very end of the page,
1301 * and at the very end of the wp_footer,
1302 *
1303 * @since 3.9.0
1304 *
1305 * @global array $wp_registered_sidebars
1306 * @global array $wp_registered_widgets
1307 */
1308 public function export_preview_data() {
1309 global $wp_registered_sidebars, $wp_registered_widgets;
1310
1311 $switched_locale = switch_to_user_locale( get_current_user_id() );
1312
1313 $l10n = array(
1314 'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
1315 );
1316
1317 if ( $switched_locale ) {
1318 restore_previous_locale();
1319 }
1320
1321 $rendered_sidebars = array_filter( $this->rendered_sidebars );
1322 $rendered_widgets = array_filter( $this->rendered_widgets );
1323
1324 // Prepare Customizer settings to pass to JavaScript.
1325 $settings = array(
1326 'renderedSidebars' => array_fill_keys( array_keys( $rendered_sidebars ), true ),
1327 'renderedWidgets' => array_fill_keys( array_keys( $rendered_widgets ), true ),
1328 'registeredSidebars' => array_values( $wp_registered_sidebars ),
1329 'registeredWidgets' => $wp_registered_widgets,
1330 'l10n' => $l10n,
1331 'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
1332 );
1333
1334 foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
1335 unset( $registered_widget['callback'] ); // May not be JSON-serializable.
1336 }
1337 wp_print_inline_script_tag(
1338 sprintf( 'var _wpWidgetCustomizerPreviewSettings = %s;', wp_json_encode( $settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) ) . "\n//# sourceURL=" . rawurlencode( __METHOD__ )
1339 );
1340 }
1341
1342 /**
1343 * Tracks the widgets that were rendered.
1344 *
1345 * @since 3.9.0
1346 *
1347 * @param array $widget Rendered widget to tally.
1348 */
1349 public function tally_rendered_widgets( $widget ) {
1350 $this->rendered_widgets[ $widget['id'] ] = true;
1351 }
1352
1353 /**
1354 * Determine if a widget is rendered on the page.
1355 *
1356 * @since 4.0.0
1357 *
1358 * @param string $widget_id Widget ID to check.
1359 * @return bool Whether the widget is rendered.
1360 */
1361 public function is_widget_rendered( $widget_id ) {
1362 return ! empty( $this->rendered_widgets[ $widget_id ] );
1363 }
1364
1365 /**
1366 * Determines if a sidebar is rendered on the page.
1367 *
1368 * @since 4.0.0
1369 *
1370 * @param string $sidebar_id Sidebar ID to check.
1371 * @return bool Whether the sidebar is rendered.
1372 */
1373 public function is_sidebar_rendered( $sidebar_id ) {
1374 return ! empty( $this->rendered_sidebars[ $sidebar_id ] );
1375 }
1376
1377 /**
1378 * Tallies the sidebars rendered via is_active_sidebar().
1379 *
1380 * Keep track of the times that is_active_sidebar() is called in the template,
1381 * and assume that this means that the sidebar would be rendered on the template
1382 * if there were widgets populating it.
1383 *
1384 * @since 3.9.0
1385 *
1386 * @param bool $is_active Whether the sidebar is active.
1387 * @param string $sidebar_id Sidebar ID.
1388 * @return bool Whether the sidebar is active.
1389 */
1390 public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
1391 if ( is_registered_sidebar( $sidebar_id ) ) {
1392 $this->rendered_sidebars[ $sidebar_id ] = true;
1393 }
1394
1395 /*
1396 * We may need to force this to true, and also force-true the value
1397 * for 'dynamic_sidebar_has_widgets' if we want to ensure that there
1398 * is an area to drop widgets into, if the sidebar is empty.
1399 */
1400 return $is_active;
1401 }
1402
1403 /**
1404 * Tallies the sidebars rendered via dynamic_sidebar().
1405 *
1406 * Keep track of the times that dynamic_sidebar() is called in the template,
1407 * and assume this means the sidebar would be rendered on the template if
1408 * there were widgets populating it.
1409 *
1410 * @since 3.9.0
1411 *
1412 * @param bool $has_widgets Whether the current sidebar has widgets.
1413 * @param string $sidebar_id Sidebar ID.
1414 * @return bool Whether the current sidebar has widgets.
1415 */
1416 public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
1417 if ( is_registered_sidebar( $sidebar_id ) ) {
1418 $this->rendered_sidebars[ $sidebar_id ] = true;
1419 }
1420
1421 /*
1422 * We may need to force this to true, and also force-true the value
1423 * for 'is_active_sidebar' if we want to ensure there is an area to
1424 * drop widgets into, if the sidebar is empty.
1425 */
1426 return $has_widgets;
1427 }
1428
1429 /**
1430 * Retrieves MAC for a serialized widget instance string.
1431 *
1432 * Allows values posted back from JS to be rejected if any tampering of the
1433 * data has occurred.
1434 *
1435 * @since 3.9.0
1436 *
1437 * @param string $serialized_instance Widget instance.
1438 * @return string MAC for serialized widget instance.
1439 */
1440 protected function get_instance_hash_key( $serialized_instance ) {
1441 return wp_hash( $serialized_instance );
1442 }
1443
1444 /**
1445 * Sanitizes a widget instance.
1446 *
1447 * Unserialize the JS-instance for storing in the options. It's important that this filter
1448 * only get applied to an instance *once*.
1449 *
1450 * @since 3.9.0
1451 * @since 5.8.0 Added the `$id_base` parameter.
1452 *
1453 * @global WP_Widget_Factory $wp_widget_factory
1454 *
1455 * @param array $value Widget instance to sanitize.
1456 * @param string $id_base Optional. Base of the ID of the widget being sanitized. Default null.
1457 * @return array|void Sanitized widget instance.
1458 */
1459 public function sanitize_widget_instance( $value, $id_base = null ) {
1460 global $wp_widget_factory;
1461
1462 if ( array() === $value ) {
1463 return $value;
1464 }
1465
1466 if ( isset( $value['raw_instance'] ) && $id_base && wp_use_widgets_block_editor() ) {
1467 $widget_object = $wp_widget_factory->get_widget_object( $id_base );
1468 if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
1469 if ( 'block' === $id_base && ! current_user_can( 'unfiltered_html' ) ) {
1470 /*
1471 * The content of the 'block' widget is not filtered on the fly while editing.
1472 * Filter the content here to prevent vulnerabilities.
1473 */
1474 $value['raw_instance']['content'] = wp_kses_post( $value['raw_instance']['content'] );
1475 }
1476
1477 return $value['raw_instance'];
1478 }
1479 }
1480
1481 if (
1482 empty( $value['is_widget_customizer_js_value'] ) ||
1483 empty( $value['instance_hash_key'] ) ||
1484 empty( $value['encoded_serialized_instance'] )
1485 ) {
1486 return;
1487 }
1488
1489 $decoded = base64_decode( $value['encoded_serialized_instance'], true );
1490 if ( false === $decoded ) {
1491 return;
1492 }
1493
1494 if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
1495 return;
1496 }
1497
1498 $instance = unserialize( $decoded );
1499 if ( false === $instance ) {
1500 return;
1501 }
1502
1503 return $instance;
1504 }
1505
1506 /**
1507 * Converts a widget instance into JSON-representable format.
1508 *
1509 * @since 3.9.0
1510 * @since 5.8.0 Added the `$id_base` parameter.
1511 *
1512 * @global WP_Widget_Factory $wp_widget_factory
1513 *
1514 * @param array $value Widget instance to convert to JSON.
1515 * @param string $id_base Optional. Base of the ID of the widget being sanitized. Default null.
1516 * @return array JSON-converted widget instance.
1517 */
1518 public function sanitize_widget_js_instance( $value, $id_base = null ) {
1519 global $wp_widget_factory;
1520
1521 if ( empty( $value['is_widget_customizer_js_value'] ) ) {
1522 $serialized = serialize( $value );
1523
1524 $js_value = array(
1525 'encoded_serialized_instance' => base64_encode( $serialized ),
1526 'title' => empty( $value['title'] ) ? '' : $value['title'],
1527 'is_widget_customizer_js_value' => true,
1528 'instance_hash_key' => $this->get_instance_hash_key( $serialized ),
1529 );
1530
1531 if ( $id_base && wp_use_widgets_block_editor() ) {
1532 $widget_object = $wp_widget_factory->get_widget_object( $id_base );
1533 if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
1534 $js_value['raw_instance'] = (object) $value;
1535 }
1536 }
1537
1538 return $js_value;
1539 }
1540
1541 return $value;
1542 }
1543
1544 /**
1545 * Strips out widget IDs for widgets which are no longer registered.
1546 *
1547 * One example where this might happen is when a plugin orphans a widget
1548 * in a sidebar upon deactivation.
1549 *
1550 * @since 3.9.0
1551 *
1552 * @global array $wp_registered_widgets
1553 *
1554 * @param array $widget_ids List of widget IDs.
1555 * @return array Parsed list of widget IDs.
1556 */
1557 public function sanitize_sidebar_widgets_js_instance( $widget_ids ) {
1558 global $wp_registered_widgets;
1559 $widget_ids = array_values( array_intersect( $widget_ids, array_keys( $wp_registered_widgets ) ) );
1560 return $widget_ids;
1561 }
1562
1563 /**
1564 * Finds and invokes the widget update and control callbacks.
1565 *
1566 * Requires that `$_POST` be populated with the instance data.
1567 *
1568 * @since 3.9.0
1569 *
1570 * @global array $wp_registered_widget_updates
1571 * @global array $wp_registered_widget_controls
1572 *
1573 * @param string $widget_id Widget ID.
1574 * @return array|WP_Error Array containing the updated widget information.
1575 * A WP_Error object, otherwise.
1576 */
1577 public function call_widget_update( $widget_id ) {
1578 global $wp_registered_widget_updates, $wp_registered_widget_controls;
1579
1580 $setting_id = $this->get_setting_id( $widget_id );
1581
1582 /*
1583 * Make sure that other setting changes have previewed since this widget
1584 * may depend on them (e.g. Menus being present for Navigation Menu widget).
1585 */
1586 if ( ! did_action( 'customize_preview_init' ) ) {
1587 foreach ( $this->manager->settings() as $setting ) {
1588 if ( $setting->id !== $setting_id ) {
1589 $setting->preview();
1590 }
1591 }
1592 }
1593
1594 $this->start_capturing_option_updates();
1595 $parsed_id = $this->parse_widget_id( $widget_id );
1596 $option_name = 'widget_' . $parsed_id['id_base'];
1597
1598 /*
1599 * If a previously-sanitized instance is provided, populate the input vars
1600 * with its values so that the widget update callback will read this instance
1601 */
1602 $added_input_vars = array();
1603 if ( ! empty( $_POST['sanitized_widget_setting'] ) ) {
1604 $sanitized_widget_setting = json_decode( $this->get_post_value( 'sanitized_widget_setting' ), true );
1605 if ( false === $sanitized_widget_setting ) {
1606 $this->stop_capturing_option_updates();
1607 return new WP_Error( 'widget_setting_malformed' );
1608 }
1609
1610 $instance = $this->sanitize_widget_instance( $sanitized_widget_setting, $parsed_id['id_base'] );
1611 if ( is_null( $instance ) ) {
1612 $this->stop_capturing_option_updates();
1613 return new WP_Error( 'widget_setting_unsanitized' );
1614 }
1615
1616 if ( ! is_null( $parsed_id['number'] ) ) {
1617 $value = array();
1618 $value[ $parsed_id['number'] ] = $instance;
1619 $key = 'widget-' . $parsed_id['id_base'];
1620 $_REQUEST[ $key ] = wp_slash( $value );
1621 $_POST[ $key ] = $_REQUEST[ $key ];
1622 $added_input_vars[] = $key;
1623 } else {
1624 foreach ( $instance as $key => $value ) {
1625 $_REQUEST[ $key ] = wp_slash( $value );
1626 $_POST[ $key ] = $_REQUEST[ $key ];
1627 $added_input_vars[] = $key;
1628 }
1629 }
1630 }
1631
1632 // Invoke the widget update callback.
1633 foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
1634 if ( $name === $parsed_id['id_base'] && is_callable( $control['callback'] ) ) {
1635 ob_start();
1636 call_user_func_array( $control['callback'], $control['params'] );
1637 ob_end_clean();
1638 break;
1639 }
1640 }
1641
1642 // Clean up any input vars that were manually added.
1643 foreach ( $added_input_vars as $key ) {
1644 unset( $_POST[ $key ] );
1645 unset( $_REQUEST[ $key ] );
1646 }
1647
1648 // Make sure the expected option was updated.
1649 if ( 0 !== $this->count_captured_options() ) {
1650 if ( $this->count_captured_options() > 1 ) {
1651 $this->stop_capturing_option_updates();
1652 return new WP_Error( 'widget_setting_too_many_options' );
1653 }
1654
1655 $updated_option_name = key( $this->get_captured_options() );
1656 if ( $updated_option_name !== $option_name ) {
1657 $this->stop_capturing_option_updates();
1658 return new WP_Error( 'widget_setting_unexpected_option' );
1659 }
1660 }
1661
1662 // Obtain the widget instance.
1663 $option = $this->get_captured_option( $option_name );
1664 if ( null !== $parsed_id['number'] ) {
1665 $instance = $option[ $parsed_id['number'] ];
1666 } else {
1667 $instance = $option;
1668 }
1669
1670 /*
1671 * Override the incoming $_POST['customized'] for a newly-created widget's
1672 * setting with the new $instance so that the preview filter currently
1673 * in place from WP_Customize_Setting::preview() will use this value
1674 * instead of the default widget instance value (an empty array).
1675 */
1676 $this->manager->set_post_value( $setting_id, $this->sanitize_widget_js_instance( $instance, $parsed_id['id_base'] ) );
1677
1678 // Obtain the widget control with the updated instance in place.
1679 ob_start();
1680 $form = $wp_registered_widget_controls[ $widget_id ];
1681 if ( $form ) {
1682 call_user_func_array( $form['callback'], $form['params'] );
1683 }
1684 $form = ob_get_clean();
1685
1686 $this->stop_capturing_option_updates();
1687
1688 return compact( 'instance', 'form' );
1689 }
1690
1691 /**
1692 * Updates widget settings asynchronously.
1693 *
1694 * Allows the Customizer to update a widget using its form, but return the new
1695 * instance info via Ajax instead of saving it to the options table.
1696 *
1697 * Most code here copied from wp_ajax_save_widget().
1698 *
1699 * @since 3.9.0
1700 *
1701 * @see wp_ajax_save_widget()
1702 */
1703 public function wp_ajax_update_widget() {
1704
1705 if ( ! is_user_logged_in() ) {
1706 wp_die( 0 );
1707 }
1708
1709 check_ajax_referer( 'update-widget', 'nonce' );
1710
1711 if ( ! current_user_can( 'edit_theme_options' ) ) {
1712 wp_die( -1 );
1713 }
1714
1715 if ( empty( $_POST['widget-id'] ) ) {
1716 wp_send_json_error( 'missing_widget-id' );
1717 }
1718
1719 /** This action is documented in wp-admin/includes/ajax-actions.php */
1720 do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
1721
1722 /** This action is documented in wp-admin/includes/ajax-actions.php */
1723 do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
1724
1725 /** This action is documented in wp-admin/widgets.php */
1726 do_action( 'sidebar_admin_setup' );
1727
1728 $widget_id = $this->get_post_value( 'widget-id' );
1729 $parsed_id = $this->parse_widget_id( $widget_id );
1730 $id_base = $parsed_id['id_base'];
1731
1732 $is_updating_widget_template = (
1733 isset( $_POST[ 'widget-' . $id_base ] )
1734 &&
1735 is_array( $_POST[ 'widget-' . $id_base ] )
1736 &&
1737 preg_match( '/__i__|%i%/', key( $_POST[ 'widget-' . $id_base ] ) )
1738 );
1739 if ( $is_updating_widget_template ) {
1740 wp_send_json_error( 'template_widget_not_updatable' );
1741 }
1742
1743 $updated_widget = $this->call_widget_update( $widget_id ); // => {instance,form}
1744 if ( is_wp_error( $updated_widget ) ) {
1745 wp_send_json_error( $updated_widget->get_error_code() );
1746 }
1747
1748 $form = $updated_widget['form'];
1749 $instance = $this->sanitize_widget_js_instance( $updated_widget['instance'], $id_base );
1750
1751 wp_send_json_success( compact( 'form', 'instance' ) );
1752 }
1753
1754 /*
1755 * Selective Refresh Methods
1756 */
1757
1758 /**
1759 * Filters arguments for dynamic widget partials.
1760 *
1761 * @since 4.5.0
1762 *
1763 * @param array|false $partial_args Partial arguments.
1764 * @param string $partial_id Partial ID.
1765 * @return array (Maybe) modified partial arguments.
1766 */
1767 public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
1768 if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
1769 return $partial_args;
1770 }
1771
1772 if ( preg_match( '/^widget\[(?P<widget_id>.+)\]$/', $partial_id, $matches ) ) {
1773 if ( false === $partial_args ) {
1774 $partial_args = array();
1775 }
1776 $partial_args = array_merge(
1777 $partial_args,
1778 array(
1779 'type' => 'widget',
1780 'render_callback' => array( $this, 'render_widget_partial' ),
1781 'container_inclusive' => true,
1782 'settings' => array( $this->get_setting_id( $matches['widget_id'] ) ),
1783 'capability' => 'edit_theme_options',
1784 )
1785 );
1786 }
1787
1788 return $partial_args;
1789 }
1790
1791 /**
1792 * Adds hooks for selective refresh.
1793 *
1794 * @since 4.5.0
1795 */
1796 public function selective_refresh_init() {
1797 if ( ! current_theme_supports( 'customize-selective-refresh-widgets' ) ) {
1798 return;
1799 }
1800 add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
1801 add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
1802 add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
1803 add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
1804 }
1805
1806 /**
1807 * Inject selective refresh data attributes into widget container elements.
1808 *
1809 * @since 4.5.0
1810 *
1811 * @see WP_Customize_Nav_Menus::filter_wp_nav_menu_args()
1812 *
1813 * @param array $params {
1814 * Dynamic sidebar params.
1815 *
1816 * @type array $args Sidebar args.
1817 * @type array $widget_args Widget args.
1818 * }
1819 * @return array Params.
1820 */
1821 public function filter_dynamic_sidebar_params( $params ) {
1822 $sidebar_args = array_merge(
1823 array(
1824 'before_widget' => '',
1825 'after_widget' => '',
1826 ),
1827 $params[0]
1828 );
1829
1830 // Skip widgets not in a registered sidebar or ones which lack a proper wrapper element to attach the data-* attributes to.
1831 $matches = array();
1832 $is_valid = (
1833 isset( $sidebar_args['id'] )
1834 &&
1835 is_registered_sidebar( $sidebar_args['id'] )
1836 &&
1837 ( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
1838 &&
1839 preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
1840 );
1841 if ( ! $is_valid ) {
1842 return $params;
1843 }
1844 $this->before_widget_tags_seen[ $matches['tag_name'] ] = true;
1845
1846 $context = array(
1847 'sidebar_id' => $sidebar_args['id'],
1848 );
1849 if ( isset( $this->context_sidebar_instance_number ) ) {
1850 $context['sidebar_instance_number'] = $this->context_sidebar_instance_number;
1851 } elseif ( isset( $sidebar_args['id'] ) && isset( $this->sidebar_instance_count[ $sidebar_args['id'] ] ) ) {
1852 $context['sidebar_instance_number'] = $this->sidebar_instance_count[ $sidebar_args['id'] ];
1853 }
1854
1855 $attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'widget[' . $sidebar_args['widget_id'] . ']' ) );
1856 $attributes .= ' data-customize-partial-type="widget"';
1857 $attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $context ) ) );
1858 $attributes .= sprintf( ' data-customize-widget-id="%s"', esc_attr( $sidebar_args['widget_id'] ) );
1859 $sidebar_args['before_widget'] = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $sidebar_args['before_widget'] );
1860
1861 $params[0] = $sidebar_args;
1862 return $params;
1863 }
1864
1865 /**
1866 * List of the tag names seen for before_widget strings.
1867 *
1868 * This is used in the {@see 'filter_wp_kses_allowed_html'} filter to ensure that the
1869 * data-* attributes can be allowed.
1870 *
1871 * @since 4.5.0
1872 * @var array
1873 */
1874 protected $before_widget_tags_seen = array();
1875
1876 /**
1877 * Ensures the HTML data-* attributes for selective refresh are allowed by kses.
1878 *
1879 * This is needed in case the `$before_widget` is run through wp_kses() when printed.
1880 *
1881 * @since 4.5.0
1882 *
1883 * @param array $allowed_html Allowed HTML.
1884 * @return array (Maybe) modified allowed HTML.
1885 */
1886 public function filter_wp_kses_allowed_data_attributes( $allowed_html ) {
1887 foreach ( array_keys( $this->before_widget_tags_seen ) as $tag_name ) {
1888 if ( ! isset( $allowed_html[ $tag_name ] ) ) {
1889 $allowed_html[ $tag_name ] = array();
1890 }
1891 $allowed_html[ $tag_name ] = array_merge(
1892 $allowed_html[ $tag_name ],
1893 array_fill_keys(
1894 array(
1895 'data-customize-partial-id',
1896 'data-customize-partial-type',
1897 'data-customize-partial-placement-context',
1898 'data-customize-partial-widget-id',
1899 'data-customize-partial-options',
1900 ),
1901 true
1902 )
1903 );
1904 }
1905 return $allowed_html;
1906 }
1907
1908 /**
1909 * Keep track of the number of times that dynamic_sidebar() was called for a given sidebar index.
1910 *
1911 * This helps facilitate the uncommon scenario where a single sidebar is rendered multiple times on a template.
1912 *
1913 * @since 4.5.0
1914 * @var array
1915 */
1916 protected $sidebar_instance_count = array();
1917
1918 /**
1919 * The current request's sidebar_instance_number context.
1920 *
1921 * @since 4.5.0
1922 * @var int|null
1923 */
1924 protected $context_sidebar_instance_number;
1925
1926 /**
1927 * Current sidebar ID being rendered.
1928 *
1929 * @since 4.5.0
1930 * @var array
1931 */
1932 protected $current_dynamic_sidebar_id_stack = array();
1933
1934 /**
1935 * Begins keeping track of the current sidebar being rendered.
1936 *
1937 * Insert marker before widgets are rendered in a dynamic sidebar.
1938 *
1939 * @since 4.5.0
1940 *
1941 * @param int|string $index Index, name, or ID of the dynamic sidebar.
1942 */
1943 public function start_dynamic_sidebar( $index ) {
1944 array_unshift( $this->current_dynamic_sidebar_id_stack, $index );
1945 if ( ! isset( $this->sidebar_instance_count[ $index ] ) ) {
1946 $this->sidebar_instance_count[ $index ] = 0;
1947 }
1948 $this->sidebar_instance_count[ $index ] += 1;
1949 if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
1950 printf( "\n<!--dynamic_sidebar_before:%s:%d-->\n", esc_html( $index ), (int) $this->sidebar_instance_count[ $index ] );
1951 }
1952 }
1953
1954 /**
1955 * Finishes keeping track of the current sidebar being rendered.
1956 *
1957 * Inserts a marker after widgets are rendered in a dynamic sidebar.
1958 *
1959 * @since 4.5.0
1960 *
1961 * @param int|string $index Index, name, or ID of the dynamic sidebar.
1962 */
1963 public function end_dynamic_sidebar( $index ) {
1964 array_shift( $this->current_dynamic_sidebar_id_stack );
1965 if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
1966 printf( "\n<!--dynamic_sidebar_after:%s:%d-->\n", esc_html( $index ), (int) $this->sidebar_instance_count[ $index ] );
1967 }
1968 }
1969
1970 /**
1971 * Current sidebar being rendered.
1972 *
1973 * @since 4.5.0
1974 * @var string|null
1975 */
1976 protected $rendering_widget_id;
1977
1978 /**
1979 * Current widget being rendered.
1980 *
1981 * @since 4.5.0
1982 * @var string|null
1983 */
1984 protected $rendering_sidebar_id;
1985
1986 /**
1987 * Filters sidebars_widgets to ensure the currently-rendered widget is the only widget in the current sidebar.
1988 *
1989 * @since 4.5.0
1990 *
1991 * @param array $sidebars_widgets Sidebars widgets.
1992 * @return array Filtered sidebars widgets.
1993 */
1994 public function filter_sidebars_widgets_for_rendering_widget( $sidebars_widgets ) {
1995 $sidebars_widgets[ $this->rendering_sidebar_id ] = array( $this->rendering_widget_id );
1996 return $sidebars_widgets;
1997 }
1998
1999 /**
2000 * Renders a specific widget using the supplied sidebar arguments.
2001 *
2002 * @since 4.5.0
2003 *
2004 * @see dynamic_sidebar()
2005 *
2006 * @param WP_Customize_Partial $partial Partial.
2007 * @param array $context {
2008 * Sidebar args supplied as container context.
2009 *
2010 * @type string $sidebar_id ID for sidebar for widget to render into.
2011 * @type int $sidebar_instance_number Disambiguating instance number.
2012 * }
2013 * @return string|false
2014 */
2015 public function render_widget_partial( $partial, $context ) {
2016 $id_data = $partial->id_data();
2017 $widget_id = array_shift( $id_data['keys'] );
2018
2019 if ( ! is_array( $context )
2020 || empty( $context['sidebar_id'] )
2021 || ! is_registered_sidebar( $context['sidebar_id'] )
2022 ) {
2023 return false;
2024 }
2025
2026 $this->rendering_sidebar_id = $context['sidebar_id'];
2027
2028 if ( isset( $context['sidebar_instance_number'] ) ) {
2029 $this->context_sidebar_instance_number = (int) $context['sidebar_instance_number'];
2030 }
2031
2032 // Filter sidebars_widgets so that only the queried widget is in the sidebar.
2033 $this->rendering_widget_id = $widget_id;
2034
2035 $filter_callback = array( $this, 'filter_sidebars_widgets_for_rendering_widget' );
2036 add_filter( 'sidebars_widgets', $filter_callback, 1000 );
2037
2038 // Render the widget.
2039 ob_start();
2040 $this->rendering_sidebar_id = $context['sidebar_id'];
2041 dynamic_sidebar( $this->rendering_sidebar_id );
2042 $container = ob_get_clean();
2043
2044 // Reset variables for next partial render.
2045 remove_filter( 'sidebars_widgets', $filter_callback, 1000 );
2046
2047 $this->context_sidebar_instance_number = null;
2048 $this->rendering_sidebar_id = null;
2049 $this->rendering_widget_id = null;
2050
2051 return $container;
2052 }
2053
2054 //
2055 // Option Update Capturing.
2056 //
2057
2058 /**
2059 * List of captured widget option updates.
2060 *
2061 * @since 3.9.0
2062 * @var array $_captured_options Values updated while option capture is happening.
2063 */
2064 protected $_captured_options = array();
2065
2066 /**
2067 * Whether option capture is currently happening.
2068 *
2069 * @since 3.9.0
2070 * @var bool $_is_current Whether option capture is currently happening or not.
2071 */
2072 protected $_is_capturing_option_updates = false;
2073
2074 /**
2075 * Determines whether the captured option update should be ignored.
2076 *
2077 * @since 3.9.0
2078 *
2079 * @param string $option_name Option name.
2080 * @return bool Whether the option capture is ignored.
2081 */
2082 protected function is_option_capture_ignored( $option_name ) {
2083 return ( str_starts_with( $option_name, '_transient_' ) );
2084 }
2085
2086 /**
2087 * Retrieves captured widget option updates.
2088 *
2089 * @since 3.9.0
2090 *
2091 * @return array Array of captured options.
2092 */
2093 protected function get_captured_options() {
2094 return $this->_captured_options;
2095 }
2096
2097 /**
2098 * Retrieves the option that was captured from being saved.
2099 *
2100 * @since 4.2.0
2101 *
2102 * @param string $option_name Option name.
2103 * @param mixed $default_value Optional. Default value to return if the option does not exist. Default false.
2104 * @return mixed Value set for the option.
2105 */
2106 protected function get_captured_option( $option_name, $default_value = false ) {
2107 if ( array_key_exists( $option_name, $this->_captured_options ) ) {
2108 $value = $this->_captured_options[ $option_name ];
2109 } else {
2110 $value = $default_value;
2111 }
2112 return $value;
2113 }
2114
2115 /**
2116 * Retrieves the number of captured widget option updates.
2117 *
2118 * @since 3.9.0
2119 *
2120 * @return int Number of updated options.
2121 */
2122 protected function count_captured_options() {
2123 return count( $this->_captured_options );
2124 }
2125
2126 /**
2127 * Begins keeping track of changes to widget options, caching new values.
2128 *
2129 * @since 3.9.0
2130 */
2131 protected function start_capturing_option_updates() {
2132 if ( $this->_is_capturing_option_updates ) {
2133 return;
2134 }
2135
2136 $this->_is_capturing_option_updates = true;
2137
2138 add_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
2139 }
2140
2141 /**
2142 * Pre-filters captured option values before updating.
2143 *
2144 * @since 3.9.0
2145 *
2146 * @param mixed $new_value The new option value.
2147 * @param string $option_name Name of the option.
2148 * @param mixed $old_value The old option value.
2149 * @return mixed Filtered option value.
2150 */
2151 public function capture_filter_pre_update_option( $new_value, $option_name, $old_value ) {
2152 if ( $this->is_option_capture_ignored( $option_name ) ) {
2153 return $new_value;
2154 }
2155
2156 if ( ! isset( $this->_captured_options[ $option_name ] ) ) {
2157 add_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
2158 }
2159
2160 $this->_captured_options[ $option_name ] = $new_value;
2161
2162 return $old_value;
2163 }
2164
2165 /**
2166 * Pre-filters captured option values before retrieving.
2167 *
2168 * @since 3.9.0
2169 *
2170 * @param mixed $value Value to return instead of the option value.
2171 * @return mixed Filtered option value.
2172 */
2173 public function capture_filter_pre_get_option( $value ) {
2174 $option_name = preg_replace( '/^pre_option_/', '', current_filter() );
2175
2176 if ( isset( $this->_captured_options[ $option_name ] ) ) {
2177 $value = $this->_captured_options[ $option_name ];
2178
2179 /** This filter is documented in wp-includes/option.php */
2180 $value = apply_filters( 'option_' . $option_name, $value, $option_name );
2181 }
2182
2183 return $value;
2184 }
2185
2186 /**
2187 * Undoes any changes to the options since options capture began.
2188 *
2189 * @since 3.9.0
2190 */
2191 protected function stop_capturing_option_updates() {
2192 if ( ! $this->_is_capturing_option_updates ) {
2193 return;
2194 }
2195
2196 remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10 );
2197
2198 foreach ( array_keys( $this->_captured_options ) as $option_name ) {
2199 remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
2200 }
2201
2202 $this->_captured_options = array();
2203 $this->_is_capturing_option_updates = false;
2204 }
2205
2206 /**
2207 * {@internal Missing Summary}
2208 *
2209 * See the {@see 'customize_dynamic_setting_args'} filter.
2210 *
2211 * @since 3.9.0
2212 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
2213 */
2214 public function setup_widget_addition_previews() {
2215 _deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
2216 }
2217
2218 /**
2219 * {@internal Missing Summary}
2220 *
2221 * See the {@see 'customize_dynamic_setting_args'} filter.
2222 *
2223 * @since 3.9.0
2224 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
2225 */
2226 public function prepreview_added_sidebars_widgets() {
2227 _deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
2228 }
2229
2230 /**
2231 * {@internal Missing Summary}
2232 *
2233 * See the {@see 'customize_dynamic_setting_args'} filter.
2234 *
2235 * @since 3.9.0
2236 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
2237 */
2238 public function prepreview_added_widget_instance() {
2239 _deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
2240 }
2241
2242 /**
2243 * {@internal Missing Summary}
2244 *
2245 * See the {@see 'customize_dynamic_setting_args'} filter.
2246 *
2247 * @since 3.9.0
2248 * @deprecated 4.2.0 Deprecated in favor of the {@see 'customize_dynamic_setting_args'} filter.
2249 */
2250 public function remove_prepreview_filters() {
2251 _deprecated_function( __METHOD__, '4.2.0', 'customize_dynamic_setting_args' );
2252 }
2253}
2254
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1