PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 13.9.2
Jetpack – WP Security, Backup, Speed, & Growth v13.9.2
12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 14.4.2 14.5.1 14.6.1 14.7.1 14.8.1 14.9.2 15.0.3 15.1.2 15.2.1 15.3.2 15.4.1 15.5.1 15.6.1 15.7.2 15.8.1 15.9.2 16.0.2 16.1.3 16.2-a.5 16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / widget-visibility / widget-conditions.php
jetpack / modules / widget-visibility Last commit date
widget-conditions 3 years ago widget-conditions.php 2 years ago
widget-conditions.php
1212 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Main class file for the Widget Visibility module.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Assets;
9
10 /**
11 * Hide or show legacy widgets conditionally.
12 *
13 * This class has two responsiblities - administrating the conditions in which legacy widgets may be hidden or shown
14 * and hiding/showing the legacy widgets on the front-end of the site, depending upon the evaluation of those conditions.
15 *
16 * Administrating the conditions can be done in one of four different WordPress screens, plus direct use of the API and
17 * is supplemented with a legacy widget preview screen. The four different admin screens are
18 *
19 * Gutenberg widget experience - widget admin (widgets.php + API + legacy widget preview)
20 * Gutenberg widget experience - Customizer (customizer screen/API + API + legacy widget preview)
21 * Classic widget experience - widget admin (widgets.php + admin-ajax XHR requests)
22 * Classic widget experience - Customizer (customizer screen/API)
23 *
24 * An introduction to the API endpoints can be found here: https://make.wordpress.org/core/2021/06/29/rest-api-changes-in-wordpress-5-8/
25 */
26 class Jetpack_Widget_Conditions {
27 /**
28 * Stores condition for template_redirect action.
29 *
30 * @var bool
31 */
32 public static $passed_template_redirect = false;
33
34 /**
35 * Class initializer.
36 */
37 public static function init() {
38 global $pagenow;
39
40 // The Gutenberg based widget experience will show a preview of legacy widgets by including a URL beginning
41 // widgets.php?legacy-widget-preview inside an iframe. Previews don't need widget editing loaded and also don't
42 // want to run the filter - if the widget is filtered out it'll be empty, which would be confusing.
43 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
44 if ( isset( $_GET['legacy-widget-preview'] ) ) {
45 return;
46 }
47
48 // If action is posted and it's save-widget then it's relevant to widget conditions, otherwise it's something
49 // else and it's not worth registering hooks.
50 // phpcs:ignore WordPress.Security.NonceVerification.Missing
51 if ( isset( $_POST['action'] ) && ! isset( $_POST['customize_changeset_uuid'] ) && ! in_array( $_POST['action'], array( 'save-widget', 'update-widget' ), true ) ) {
52 return;
53 }
54
55 // API call to *list* the widget types doesn't use editing visibility or display widgets.
56 if ( isset( $_SERVER['REQUEST_URI'] ) && str_contains( $_SERVER['REQUEST_URI'], '/widget-types?' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
57 return;
58 }
59
60 $add_data_assets_to_page = false;
61 $add_html_to_form = false;
62 $handle_widget_updates = false;
63 $add_block_controls = false;
64
65 // Check to see if using the customizer, but not using the preview. The preview should filter out widgets,
66 // the customizer controls in the sidebar should not (so they can be edited).
67 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
68 $customizer_not_previewer = is_customize_preview() && ! isset( $_GET['customize_changeset_uuid'] );
69 $using_classic_experience = ! wp_use_widgets_block_editor();
70 if ( $using_classic_experience &&
71 ( $customizer_not_previewer || 'widgets.php' === $pagenow ||
72 // phpcs:ignore WordPress.Security.NonceVerification.Missing
73 ( 'admin-ajax.php' === $pagenow && array_key_exists( 'action', $_POST ) && 'save-widget' === $_POST['action'] )
74 )
75 ) {
76 $add_data_assets_to_page = true;
77 $add_html_to_form = true;
78 $handle_widget_updates = true;
79 } else {
80 // On a screen that is hosting the API in the gutenberg editing experience.
81 if ( $customizer_not_previewer || 'widgets.php' === $pagenow ) {
82 $add_data_assets_to_page = true;
83 $add_block_controls = true;
84 }
85
86 // Encoding for a particular widget end point.
87 if ( isset( $_SERVER['REQUEST_URI'] ) && 1 === preg_match( '|/widget-types/.*/encode|', $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
88 $add_html_to_form = true;
89 $handle_widget_updates = true;
90 }
91
92 // Batch API is usually saving but could be anything.
93 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
94 if ( str_contains( $current_url, '/wp-json/batch/v1' ) || 1 === preg_match( '/^\/wp\/v2\/sites\/\d+\/batch\/v1/', $current_url ) ) {
95 $handle_widget_updates = true;
96 $add_html_to_form = true;
97 }
98
99 // Saving widgets via non-batch API. This isn't used within WordPress but could be used by third parties in theory.
100 if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] && str_contains( $_SERVER['REQUEST_URI'], '/wp/v2/widgets' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
101 $handle_widget_updates = true;
102 $add_html_to_form = true;
103 }
104 }
105
106 if ( $add_html_to_form ) {
107 add_action( 'in_widget_form', array( __CLASS__, 'widget_conditions_admin' ), 10, 3 );
108 }
109
110 if ( $handle_widget_updates ) {
111 add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
112 }
113
114 if ( $add_data_assets_to_page ) {
115 add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
116 }
117
118 if ( $add_block_controls ) {
119 add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'setup_block_controls' ) );
120 }
121
122 if ( ! $add_html_to_form && ! $handle_widget_updates && ! $add_data_assets_to_page &&
123 ! in_array( $pagenow, array( 'wp-login.php', 'wp-register.php' ), true )
124 ) {
125 // Not hit any known widget admin endpoint, register widget display hooks instead.
126 add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
127 add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
128 add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ) );
129 }
130 }
131
132 /**
133 * Enqueue the block-based widget visibility scripts.
134 */
135 public static function setup_block_controls() {
136 Assets::register_script(
137 'widget-visibility-editor',
138 '_inc/build/widget-visibility/editor/index.js',
139 JETPACK__PLUGIN_FILE,
140 array(
141 'in_footer' => true,
142 'textdomain' => 'jetpack',
143 )
144 );
145 Assets::enqueue_script( 'widget-visibility-editor' );
146 }
147
148 /**
149 * Add the 'conditions' attribute, where visibility rules are stored, to some blocks.
150 *
151 * We normally add the block attributes in the browser's javascript env only,
152 * but these blocks use a ServerSideRender dynamic preview, so the php env needs
153 * to know about the new attribute, too.
154 */
155 public static function add_block_attributes_filter() {
156 $blocks = array(
157 // These use <ServerSideRender>.
158 'core/calendar',
159 'core/latest-comments',
160 'core/rss',
161 'core/archives',
162 'core/tag-cloud',
163 'core/page-list',
164 'core/latest-posts',
165 'woocommerce/product-categories',
166 );
167 /**
168 * Filters the list of widget visibility blocks using <ServerSideRender>.
169 *
170 * @since 12.4
171 *
172 * @module widget-visibility
173 *
174 * @param string[] $blocks Array of block names from WordPress core and WooCommerce.
175 */
176 $blocks_to_add_visibility_conditions = apply_filters( 'jetpack_widget_visibility_server_side_render_blocks', $blocks );
177
178 /**
179 * Block registration filter callback.
180 *
181 * @param array $settings Array of arguments for registering a block type.
182 * @param string $name Block type name including namespace.
183 * @return array
184 */
185 $filter_metadata_registration = function ( $settings, $name ) use ( $blocks_to_add_visibility_conditions ) {
186 if ( in_array( $name, $blocks_to_add_visibility_conditions, true ) && ! empty( $settings['attributes'] ) ) {
187 $settings['attributes']['conditions'] = array(
188 'type' => 'object',
189 );
190 }
191 return $settings;
192 };
193
194 add_filter( 'register_block_type_args', $filter_metadata_registration, 10, 2 );
195 }
196
197 /**
198 * Prepare the interface for editing widgets - loading css, javascript & data
199 */
200 public static function widget_admin_setup() {
201 wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ), array( 'widgets' ), JETPACK__VERSION );
202 wp_style_add_data( 'widget-conditions', 'rtl', 'replace' );
203 wp_enqueue_script(
204 'widget-conditions',
205 Assets::get_file_url_for_environment(
206 '_inc/build/widget-visibility/widget-conditions/widget-conditions.min.js',
207 'modules/widget-visibility/widget-conditions/widget-conditions.js'
208 ),
209 array( 'jquery', 'jquery-ui-core' ),
210 JETPACK__VERSION,
211 true
212 );
213
214 // Set up a single copy of all of the data that Widget Visibility needs.
215 // This allows all widget conditions to reuse the same data, keeping page size down
216 // and eliminating the AJAX calls we used to have to use to fetch the minor rule options.
217 $widget_conditions_data = array();
218
219 $widget_conditions_data['category'] = array();
220 $widget_conditions_data['category'][] = array( '', __( 'All category pages', 'jetpack' ) );
221
222 $categories = get_categories(
223 array(
224 /**
225 * Specific a maximum number of categories to query for the Widget visibility UI.
226 *
227 * @module widget-visibility
228 *
229 * @since 9.1.0
230 *
231 * @param int $number Maximum number of categories displayed in the Widget visibility UI.
232 */
233 'number' => (int) apply_filters( 'jetpack_widget_visibility_max_number_categories', 1000 ),
234 'orderby' => 'count',
235 'order' => 'DESC',
236 )
237 );
238 usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
239
240 foreach ( $categories as $category ) {
241 $widget_conditions_data['category'][] = array( (string) $category->term_id, $category->name );
242 }
243
244 $widget_conditions_data['loggedin'] = array();
245 $widget_conditions_data['loggedin'][] = array( 'loggedin', __( 'Logged In', 'jetpack' ) );
246 $widget_conditions_data['loggedin'][] = array( 'loggedout', __( 'Logged Out', 'jetpack' ) );
247
248 $widget_conditions_data['author'] = array();
249 $widget_conditions_data['author'][] = array( '', __( 'All author pages', 'jetpack' ) );
250
251 /*
252 * Query for users with publish caps.
253 */
254 $authors_args = array(
255 'orderby' => 'name',
256 'capability' => array( 'edit_posts' ),
257 'fields' => array( 'ID', 'display_name' ),
258 );
259
260 $authors = get_users( $authors_args );
261
262 foreach ( $authors as $author ) {
263 $widget_conditions_data['author'][] = array( (string) $author->ID, $author->display_name );
264 }
265
266 $widget_conditions_data['role'] = array();
267
268 global $wp_roles;
269
270 foreach ( $wp_roles->roles as $role_key => $role ) {
271 $widget_conditions_data['role'][] = array( (string) $role_key, $role['name'] );
272 }
273
274 $widget_conditions_data['tag'] = array();
275 $widget_conditions_data['tag'][] = array( '', __( 'All tag pages', 'jetpack' ) );
276
277 $tags = get_tags(
278 array(
279 /**
280 * Specific a maximum number of tags to query for the Widget visibility UI.
281 *
282 * @module widget-visibility
283 *
284 * @since 9.1.0
285 *
286 * @param int $number Maximum number of tags displayed in the Widget visibility UI.
287 */
288 'number' => (int) apply_filters( 'jetpack_widget_visibility_max_number_tags', 1000 ),
289 'orderby' => 'count',
290 'order' => 'DESC',
291 )
292 );
293 usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
294
295 foreach ( $tags as $tag ) {
296 $widget_conditions_data['tag'][] = array( (string) $tag->term_id, $tag->name );
297 }
298
299 $widget_conditions_data['date'] = array();
300 $widget_conditions_data['date'][] = array( '', __( 'All date archives', 'jetpack' ) );
301 $widget_conditions_data['date'][] = array( 'day', __( 'Daily archives', 'jetpack' ) );
302 $widget_conditions_data['date'][] = array( 'month', __( 'Monthly archives', 'jetpack' ) );
303 $widget_conditions_data['date'][] = array( 'year', __( 'Yearly archives', 'jetpack' ) );
304
305 $widget_conditions_data['page'] = array();
306 $widget_conditions_data['page'][] = array( 'front', __( 'Front page', 'jetpack' ) );
307 $widget_conditions_data['page'][] = array( 'posts', __( 'Posts page', 'jetpack' ) );
308 $widget_conditions_data['page'][] = array( 'archive', __( 'Archive page', 'jetpack' ) );
309 $widget_conditions_data['page'][] = array( '404', __( '404 error page', 'jetpack' ) );
310 $widget_conditions_data['page'][] = array( 'search', __( 'Search results', 'jetpack' ) );
311
312 $post_types = get_post_types( array( 'public' => true ), 'objects' );
313
314 $widget_conditions_post_types = array();
315 $widget_conditions_post_type_archives = array();
316
317 foreach ( $post_types as $post_type ) {
318 $widget_conditions_post_types[] = array( 'post_type-' . $post_type->name, $post_type->labels->singular_name );
319 $widget_conditions_post_type_archives[] = array( 'post_type_archive-' . $post_type->name, $post_type->labels->name );
320 }
321
322 $widget_conditions_data['page'][] = array( __( 'Post type:', 'jetpack' ), $widget_conditions_post_types );
323
324 $widget_conditions_data['page'][] = array( __( 'Post type Archives:', 'jetpack' ), $widget_conditions_post_type_archives );
325
326 $pages = self::get_pages();
327
328 $dropdown_tree_args = array(
329 'depth' => 0,
330 'child_of' => 0,
331 'selected' => 0,
332 'echo' => false,
333 'name' => 'page_id',
334 'id' => '',
335 'class' => '',
336 'show_option_none' => '',
337 'show_option_no_change' => '',
338 'option_none_value' => '',
339 'value_field' => 'ID',
340 );
341 $pages_dropdown = walk_page_dropdown_tree( $pages, 0, $dropdown_tree_args );
342 preg_match_all( '/value=.([0-9]+).[^>]*>([^<]+)</', $pages_dropdown, $page_ids_and_titles, PREG_SET_ORDER );
343 $static_pages = array();
344
345 foreach ( $page_ids_and_titles as $page_id_and_title ) {
346 $static_pages[] = array( (string) $page_id_and_title[1], $page_id_and_title[2] );
347 }
348
349 $widget_conditions_data['page'][] = array( __( 'Static page:', 'jetpack' ), $static_pages );
350
351 $widget_conditions_data['taxonomy'] = array();
352 $widget_conditions_data['taxonomy'][] = array( '', __( 'All taxonomy pages', 'jetpack' ) );
353
354 $taxonomies = get_taxonomies(
355 /**
356 * Filters args passed to get_taxonomies.
357 *
358 * @see https://developer.wordpress.org/reference/functions/get_taxonomies/
359 *
360 * @since 5.3.0
361 *
362 * @module widget-visibility
363 *
364 * @param array $args Widget Visibility taxonomy arguments.
365 */
366 apply_filters( 'jetpack_widget_visibility_tax_args', array( '_builtin' => false ) ),
367 'objects'
368 );
369
370 usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
371
372 foreach ( $taxonomies as $taxonomy ) {
373 $taxonomy_terms = get_terms(
374 array( $taxonomy->name ),
375 array(
376 'number' => 250,
377 'hide_empty' => false,
378 )
379 );
380
381 $widget_conditions_terms = array();
382 $widget_conditions_terms[] = array( $taxonomy->name, $taxonomy->labels->all_items );
383
384 foreach ( $taxonomy_terms as $term ) {
385 $widget_conditions_terms[] = array( $taxonomy->name . '_tax_' . $term->term_id, $term->name );
386 }
387
388 $widget_conditions_data['taxonomy'][] = array( $taxonomy->labels->name . ':', $widget_conditions_terms );
389 }
390
391 wp_localize_script( 'widget-conditions', 'widget_conditions_data', $widget_conditions_data );
392
393 // Save a list of the IDs of all pages that have children for dynamically showing the "Include children" checkbox.
394 $all_pages = self::get_pages();
395 $all_parents = array();
396
397 foreach ( $all_pages as $page ) {
398 if ( $page->post_parent ) {
399 $all_parents[ (string) $page->post_parent ] = true;
400 }
401 }
402
403 $front_page_id = get_option( 'page_on_front' );
404
405 if ( isset( $all_parents[ $front_page_id ] ) ) {
406 $all_parents['front'] = true;
407 }
408
409 wp_localize_script( 'widget-conditions', 'widget_conditions_parent_pages', $all_parents );
410 }
411
412 /**
413 * Retrieves a full list of all pages, containing just the IDs, post_parent, post_title, and post_status fields.
414 *
415 * Since the WordPress' `get_pages` function does not allow us to fetch only the fields mentioned
416 * above, we need to introduce a custom method using a direct SQL query fetching those.
417 *
418 * By fetching only those four fields and not populating the object cache for all the pages, we can
419 * improve the performance of the query on sites having a lot of pages.
420 *
421 * @see https://core.trac.wordpress.org/ticket/51469
422 *
423 * @return array List of all pages on the site (stdClass objects containing ID, post_title, post_parent, and post_status only).
424 */
425 public static function get_pages() {
426 global $wpdb;
427
428 $last_changed = wp_cache_get_last_changed( 'posts' );
429 $cache_key = "get_pages:$last_changed";
430 $pages = wp_cache_get( $cache_key, 'widget_conditions' );
431 if ( false === $pages ) {
432 $pages = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_parent, {$wpdb->posts}.post_title, {$wpdb->posts}.post_status FROM {$wpdb->posts} WHERE {$wpdb->posts}.post_type = 'page' AND {$wpdb->posts}.post_status = 'publish' ORDER BY {$wpdb->posts}.post_title ASC" );
433 wp_cache_set( $cache_key, $pages, 'widget_conditions' );
434 }
435
436 // Copy-pasted from the get_pages function. For usage in the `widget_conditions_get_pages` filter.
437 $parsed_args = array(
438 'child_of' => 0,
439 'sort_order' => 'ASC',
440 'sort_column' => 'post_title',
441 'hierarchical' => 1,
442 'exclude' => array(),
443 'include' => array(),
444 'meta_key' => '',
445 'meta_value' => '',
446 'authors' => '',
447 'parent' => -1,
448 'exclude_tree' => array(),
449 'number' => '',
450 'offset' => 0,
451 'post_type' => 'page',
452 'post_status' => 'publish',
453 );
454
455 /**
456 * Filters the retrieved list of pages.
457 *
458 * @since 9.1.0
459 *
460 * @module widget-visibility
461 *
462 * @param stdClass[] $pages Array of objects containing only the ID, post_parent, post_title, and post_status fields.
463 * @param array $parsed_args Array of get_pages() arguments.
464 */
465 return apply_filters( 'jetpack_widget_visibility_get_pages', $pages, $parsed_args );
466 }
467
468 /**
469 * Add the widget conditions to each widget in the admin.
470 *
471 * @param WP_Widget $widget Widget to add conditions settings to.
472 * @param null $return unused.
473 * @param array $instance The widget settings.
474 */
475 public static function widget_conditions_admin( $widget, $return, $instance ) {
476 $conditions = array();
477
478 if ( isset( $instance['conditions'] ) ) {
479 $conditions = $instance['conditions'];
480 }
481
482 if ( ! isset( $conditions['action'] ) ) {
483 $conditions['action'] = 'show';
484 }
485
486 if ( empty( $conditions['rules'] ) ) {
487 $conditions['rules'][] = array(
488 'major' => '',
489 'minor' => '',
490 'has_children' => '',
491 );
492 }
493
494 if ( empty( $conditions['match_all'] ) ) {
495 $conditions['match_all'] = false;
496 }
497
498 ?>
499 <div
500 class="
501 widget-conditional
502 <?php
503 // $_POST['widget-conditions-visible'] is used in the classic widget experience to decide whether to
504 // display the visibility panel open, e.g. when saving. In the gutenberg widget experience the POST
505 // value will always be empty, but this is fine - it doesn't rerender the HTML when saving anyway.
506 if (
507 // phpcs:ignore WordPress.Security.NonceVerification.Missing
508 empty( $_POST['widget-conditions-visible'] ) || '0' === $_POST['widget-conditions-visible']
509 ) {
510 ?>
511 widget-conditional-hide
512 <?php
513 }
514 ?>
515 <?php
516 if ( ! empty( $conditions['match_all'] ) && $conditions['match_all'] ) {
517 ?>
518 intersection
519 <?php
520 } else {
521 ?>
522 conjunction
523 <?php
524 }
525 ?>
526 ">
527 <input type="hidden" name="widget-conditions-visible" value="
528 <?php
529 if ( isset( $_POST['widget-conditions-visible'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
530 echo esc_attr( filter_var( wp_unslash( $_POST['widget-conditions-visible'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
531 } else {
532 echo 0;
533 }
534 ?>
535 " />
536 <?php
537 if ( ! isset( $_POST['widget-conditions-visible'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
538 ?>
539 <a href="#" class="button display-options"><?php esc_html_e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
540 <div class="widget-conditional-inner">
541 <div class="condition-top">
542 <?php
543 printf(
544 // translators: %s is a HTML select widget for widget visibility, 'show' and 'hide' are it's options. It will read like 'show if' or 'hide if'.
545 esc_html_x( '%s if:', 'placeholder: dropdown menu to select widget visibility; hide if or show if', 'jetpack' ),
546 '<select name="' . esc_attr( $widget->get_field_name( 'conditions[action]' ) ) . '">
547 <option value="show" ' . selected( $conditions['action'], 'show', false ) . '>' . esc_html_x( 'Show', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option>
548 <option value="hide" ' . selected( $conditions['action'], 'hide', false ) . '>' . esc_html_x( 'Hide', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option>
549 </select>'
550 );
551 ?>
552 </div><!-- .condition-top -->
553
554 <div class="conditions">
555 <?php
556
557 foreach ( $conditions['rules'] as $rule_index => $rule ) {
558 $rule = wp_parse_args(
559 $rule,
560 array(
561 'major' => '',
562 'minor' => '',
563 'has_children' => '',
564 )
565 );
566 ?>
567 <div class="condition" data-rule-major="<?php echo esc_attr( $rule['major'] ); ?>" data-rule-minor="<?php echo esc_attr( $rule['minor'] ); ?>" data-rule-has-children="<?php echo esc_attr( $rule['has_children'] ); ?>">
568 <div class="selection alignleft">
569 <select class="conditions-rule-major" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_major][]' ) ); ?>">
570 <option value="" <?php selected( '', $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
571 <option value="category" <?php selected( 'category', $rule['major'] ); ?>><?php esc_html_e( 'Category', 'jetpack' ); ?></option>
572 <option value="author" <?php selected( 'author', $rule['major'] ); ?>><?php echo esc_html_x( 'Author', 'Noun, as in: "The author of this post is..."', 'jetpack' ); ?></option>
573
574 <?php if ( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { // this doesn't work on .com because of caching. ?>
575 <option value="loggedin" <?php selected( 'loggedin', $rule['major'] ); ?>><?php echo esc_html_x( 'User', 'Noun', 'jetpack' ); ?></option>
576 <option value="role" <?php selected( 'role', $rule['major'] ); ?>><?php echo esc_html_x( 'Role', 'Noun, as in: "The user role of that can access this widget is..."', 'jetpack' ); ?></option>
577 <?php } ?>
578
579 <option value="tag" <?php selected( 'tag', $rule['major'] ); ?>><?php echo esc_html_x( 'Tag', 'Noun, as in: "This post has one tag."', 'jetpack' ); ?></option>
580 <option value="date" <?php selected( 'date', $rule['major'] ); ?>><?php echo esc_html_x( 'Date', 'Noun, as in: "This page is a date archive."', 'jetpack' ); ?></option>
581 <option value="page" <?php selected( 'page', $rule['major'] ); ?>><?php echo esc_html_x( 'Page', 'Example: The user is looking at a page, not a post.', 'jetpack' ); ?></option>
582 <?php if ( get_taxonomies( array( '_builtin' => false ) ) ) : ?>
583 <option value="taxonomy" <?php selected( 'taxonomy', $rule['major'] ); ?>><?php echo esc_html_x( 'Taxonomy', 'Noun, as in: "This post has one taxonomy."', 'jetpack' ); ?></option>
584 <?php endif; ?>
585 </select>
586
587 <?php echo esc_html_x( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>
588
589 <select class="conditions-rule-minor" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_minor][]' ) ); ?>"
590 <?php
591 if ( ! $rule['major'] ) {
592 echo ' disabled="disabled"';
593 }
594 ?>
595 >
596 <?php
597 /*
598 Include the currently selected value so that if the widget is saved without
599 expanding the Visibility section, we don't lose the minor part of the rule.
600 If it is opened, this list is cleared out and populated with all the values.
601 */
602 ?>
603 <option value="<?php echo esc_attr( $rule['minor'] ); ?>" selected="selected"></option>
604 </select>
605
606 <span class="conditions-rule-has-children"
607 <?php
608 if ( ! $rule['has_children'] ) {
609 echo ' style="display: none;"';
610 }
611 ?>
612 >
613 <label>
614 <input type="checkbox" name="<?php echo esc_attr( $widget->get_field_name( "conditions[page_children][$rule_index]" ) ); ?>" value="has" <?php checked( $rule['has_children'], true ); ?> />
615 <?php echo esc_html_x( 'Include children', 'Checkbox on Widget Visibility if children of the selected page should be included in the visibility rule.', 'jetpack' ); ?>
616 </label>
617 </span>
618 </div>
619
620 <div class="condition-control">
621 <span class="condition-conjunction">
622 <?php echo esc_html_x( 'or', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
623 </span>
624 <span class="condition-intersection">
625 <?php echo esc_html_x( 'and', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
626 </span>
627 <div class="actions alignright">
628 <a href="#" class="delete-condition dashicons dashicons-no"><?php esc_html_e( 'Delete', 'jetpack' ); ?></a><a href="#" class="add-condition dashicons dashicons-plus"><?php esc_html_e( 'Add', 'jetpack' ); ?></a>
629 </div>
630 </div>
631
632 </div><!-- .condition -->
633 <?php
634 }
635
636 ?>
637 </div><!-- .conditions -->
638 <div class="conditions">
639 <div class="condition-top">
640 <label>
641 <input
642 type="checkbox"
643 name="<?php echo esc_attr( $widget->get_field_name( 'conditions[match_all]' ) ); ?>"
644 value="1"
645 class="conditions-match-all"
646 <?php checked( $conditions['match_all'], '1' ); ?> />
647 <?php esc_html_e( 'Match all conditions', 'jetpack' ); ?>
648 </label>
649 </div><!-- .condition-top -->
650 </div><!-- .conditions -->
651 </div><!-- .widget-conditional-inner -->
652 </div><!-- .widget-conditional -->
653 <?php
654 }
655
656 /**
657 * On an AJAX update of the widget settings, process the display conditions.
658 *
659 * @param array $instance The current instance's settings.
660 * @param array $new_instance New settings for this instance as input by the user.
661 * @param array $old_instance Old settings for this instance.
662 * @return array Modified settings.
663 */
664 public static function widget_update( $instance, $new_instance, $old_instance ) {
665 $conditions = array();
666 $conditions['action'] = isset( $new_instance['conditions']['action'] ) ? $new_instance['conditions']['action'] : null;
667 $conditions['match_all'] = ! empty( $new_instance['conditions']['match_all'] ) ? '1' : '0';
668 $conditions['rules'] = isset( $new_instance['conditions']['rules'] ) ? $new_instance['conditions']['rules'] : array();
669
670 if ( isset( $new_instance['conditions']['rules_major'] ) ) {
671 foreach ( $new_instance['conditions']['rules_major'] as $index => $major_rule ) {
672 if ( ! $major_rule ) {
673 continue;
674 }
675
676 $conditions['rules'][] = array(
677 'major' => $major_rule,
678 'minor' => isset( $new_instance['conditions']['rules_minor'][ $index ] ) ? $new_instance['conditions']['rules_minor'][ $index ] : '',
679 'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ) ? true : false,
680 );
681 }
682 }
683
684 if ( ! empty( $conditions['rules'] ) ) {
685 $instance['conditions'] = $conditions;
686 } elseif ( empty( $new_instance['conditions']['rules'] ) ) {
687 unset( $instance['conditions'] );
688 }
689
690 if (
691 ( isset( $instance['conditions'] ) && ! isset( $old_instance['conditions'] ) )
692 ||
693 (
694 isset( $instance['conditions'], $old_instance['conditions'] )
695 &&
696 serialize( $instance['conditions'] ) !== serialize( $old_instance['conditions'] ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
697 )
698 ) {
699
700 /**
701 * Fires after the widget visibility conditions are saved.
702 *
703 * @module widget-visibility
704 *
705 * @since 2.4.0
706 */
707 do_action( 'widget_conditions_save' );
708 } elseif ( ! isset( $instance['conditions'] ) && isset( $old_instance['conditions'] ) ) {
709
710 /**
711 * Fires after the widget visibility conditions are deleted.
712 *
713 * @module widget-visibility
714 *
715 * @since 2.4.0
716 */
717 do_action( 'widget_conditions_delete' );
718 }
719
720 return $instance;
721 }
722
723 /**
724 * Filter the list of widgets for a sidebar so that active sidebars work as expected.
725 *
726 * @param array $widget_areas An array of widget areas and their widgets.
727 * @return array The modified $widget_area array.
728 */
729 public static function sidebars_widgets( $widget_areas ) {
730 $settings = array();
731
732 if ( ! is_array( $widget_areas ) ) {
733 return $widget_areas;
734 }
735
736 foreach ( $widget_areas as $widget_area => $widgets ) {
737 if ( empty( $widgets ) ) {
738 continue;
739 }
740
741 if ( ! is_array( $widgets ) ) {
742 continue;
743 }
744
745 if ( 'wp_inactive_widgets' === $widget_area ) {
746 continue;
747 }
748
749 foreach ( $widgets as $position => $widget_id ) {
750 // Find the conditions for this widget.
751 if ( preg_match( '/^(.+?)-(\d+)$/', $widget_id, $matches ) ) {
752 $id_base = $matches[1];
753 $widget_number = (int) $matches[2];
754 } else {
755 $id_base = $widget_id;
756 $widget_number = null;
757 }
758
759 if ( ! isset( $settings[ $id_base ] ) ) {
760 $settings[ $id_base ] = get_option( 'widget_' . $id_base );
761 }
762
763 // New multi widget (WP_Widget).
764 if ( $widget_number !== null ) {
765 if ( isset( $settings[ $id_base ][ $widget_number ] ) && false === self::filter_widget( $settings[ $id_base ][ $widget_number ] ) ) {
766 unset( $widget_areas[ $widget_area ][ $position ] );
767 }
768 } elseif ( ! empty( $settings[ $id_base ] ) && false === self::filter_widget( $settings[ $id_base ] ) ) { // Old single widget.
769 unset( $widget_areas[ $widget_area ][ $position ] );
770 }
771 }
772 }
773
774 return $widget_areas;
775 }
776
777 /**
778 * Set field $passed_template_redirect to true.
779 */
780 public static function template_redirect() {
781 self::$passed_template_redirect = true;
782 }
783
784 /**
785 * Generates a condition key based on the rule array.
786 *
787 * @param array $rule rule data.
788 * @return string key used to retrieve the condition.
789 */
790 public static function generate_condition_key( $rule ) {
791 if ( isset( $rule['has_children'] ) ) {
792 return $rule['major'] . ':' . $rule['minor'] . ':' . $rule['has_children'];
793 }
794 return $rule['major'] . ':' . $rule['minor'];
795 }
796
797 /**
798 * Determine whether the widget should be displayed based on conditions set by the user.
799 *
800 * @param array $instance The widget settings.
801 * @return array Settings to display or bool false to hide.
802 */
803 public static function filter_widget( $instance ) {
804 // Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
805 // filtered out and become impossible to edit.
806 if ( strpos( wp_get_raw_referer(), '/wp-admin/widgets.php' ) && isset( $_SERVER['REQUEST_URI'] ) && str_contains( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/wp-json/' ) ) {
807 return $instance;
808 }
809 // WordPress.com specific check - here, referer ends in /rest-proxy/ and doesn't tell us what's requesting.
810 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
811 $nonce = ! empty( $_REQUEST['_gutenberg_nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_gutenberg_nonce'] ) ) : '';
812 $context = ! empty( $_REQUEST['context'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['context'] ) ) : '';
813 if ( wp_verify_nonce( $nonce, 'gutenberg_request' ) &&
814 1 === preg_match( '~^/wp/v2/sites/\d+/(sidebars|widgets)~', $current_url ) && 'edit' === $context ) {
815 return $instance;
816 }
817
818 if ( ! empty( $instance['conditions']['rules'] ) ) {
819 // Legacy widgets: visibility found.
820 if ( self::filter_widget_check_conditions( $instance['conditions'] ) ) {
821 return $instance;
822 }
823 return false;
824 } elseif ( ! empty( $instance['content'] ) && has_blocks( $instance['content'] ) ) {
825 // Block-Based widgets: We have gutenberg blocks that could have the 'conditions' attribute.
826 $blocks = parse_blocks( $instance['content'] );
827 if ( empty( $blocks[0]['attrs']['conditions']['rules'] ) ) {
828 // No Rules: Display widget.
829 return $instance;
830 }
831 if ( self::filter_widget_check_conditions( $blocks[0]['attrs']['conditions'] ) ) {
832 // Rules passed checks: Display widget.
833 return $instance;
834 }
835 // Rules failed checks: Hide widget.
836 return false;
837 }
838
839 // No visibility found.
840 return $instance;
841 }
842
843 /**
844 * Determine whether the widget should be displayed based on conditions set by the user.
845 *
846 * @param array $conditions Visibility Conditions: An array with keys 'rules', 'action', and 'match_all'.
847 * @return bool If the widget controlled by these conditions should show.
848 */
849 public static function filter_widget_check_conditions( $conditions ) {
850 global $wp_query;
851 // Store the results of all in-page condition lookups so that multiple widgets with
852 // the same visibility conditions don't result in duplicate DB queries.
853 static $condition_result_cache = array();
854
855 $condition_result = false;
856
857 foreach ( $conditions['rules'] as $rule ) {
858 $condition_result = false;
859 $condition_key = self::generate_condition_key( $rule );
860
861 if ( isset( $condition_result_cache[ $condition_key ] ) ) {
862 $condition_result = $condition_result_cache[ $condition_key ];
863 } else {
864 switch ( $rule['major'] ) {
865 case 'date':
866 switch ( $rule['minor'] ) {
867 case '':
868 $condition_result = is_date();
869 break;
870 case 'month':
871 $condition_result = is_month();
872 break;
873 case 'day':
874 $condition_result = is_day();
875 break;
876 case 'year':
877 $condition_result = is_year();
878 break;
879 }
880 break;
881 case 'page':
882 // Previously hardcoded post type options.
883 if ( 'post' === $rule['minor'] ) {
884 $rule['minor'] = 'post_type-post';
885 } elseif ( ! $rule['minor'] ) {
886 $rule['minor'] = 'post_type-page';
887 }
888
889 switch ( $rule['minor'] ) {
890 case '404':
891 $condition_result = is_404();
892 break;
893 case 'search':
894 $condition_result = is_search();
895 break;
896 case 'archive':
897 $condition_result = is_archive();
898 break;
899 case 'posts':
900 $condition_result = $wp_query->is_posts_page;
901 break;
902 case 'home':
903 $condition_result = is_home();
904 break;
905 case 'front':
906 if ( current_theme_supports( 'infinite-scroll' ) ) {
907 $condition_result = is_front_page();
908 } else {
909 $condition_result = is_front_page() && ! is_paged();
910 }
911 break;
912 default:
913 if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
914 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
915 } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
916 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
917 } elseif ( get_option( 'page_for_posts' ) === $rule['minor'] ) {
918 // If $rule['minor'] is a page ID which is also the posts page.
919 $condition_result = $wp_query->is_posts_page;
920 } else {
921 // $rule['minor'] is a page ID
922 $condition_result = is_page() && ( get_the_ID() === (int) $rule['minor'] );
923
924 // Check if $rule['minor'] is parent of page ID.
925 if ( ! $condition_result && isset( $rule['has_children'] ) && $rule['has_children'] ) {
926 $condition_result = wp_get_post_parent_id( get_the_ID() ) === (int) $rule['minor'];
927 }
928 }
929 break;
930 }
931 break;
932 case 'tag':
933 // All tag pages.
934 if ( ! $rule['minor'] ) {
935 if ( is_tag() ) {
936 $condition_result = true;
937 } elseif ( is_singular() ) {
938 if ( in_array( 'post_tag', get_post_taxonomies(), true ) ) {
939 $condition_result = true;
940 }
941 }
942 break;
943 }
944
945 // All pages with the specified tag term.
946 if ( is_tag( $rule['minor'] ) ) {
947 $condition_result = true;
948 } elseif ( is_singular() && has_term( $rule['minor'], 'post_tag' ) ) {
949 $condition_result = true;
950 }
951 break;
952 case 'category':
953 // All category pages.
954 if ( ! $rule['minor'] ) {
955 if ( is_category() ) {
956 $condition_result = true;
957 } elseif ( is_singular() ) {
958 if ( in_array( 'category', get_post_taxonomies(), true ) ) {
959 $condition_result = true;
960 }
961 }
962 break;
963 }
964
965 // All pages with the specified category term.
966 if ( is_category( $rule['minor'] ) ) {
967 $condition_result = true;
968 } elseif ( is_singular() && has_term( $rule['minor'], 'category' ) ) {
969 $condition_result = true;
970 }
971 break;
972 case 'loggedin':
973 $condition_result = is_user_logged_in();
974 if ( 'loggedin' !== $rule['minor'] ) {
975 $condition_result = ! $condition_result;
976 }
977 break;
978 case 'author':
979 $post = get_post();
980 if ( ! $rule['minor'] && is_author() ) {
981 $condition_result = true;
982 } elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
983 $condition_result = true;
984 } elseif ( is_singular() && $rule['minor'] && $rule['minor'] === $post->post_author ) {
985 $condition_result = true;
986 }
987 break;
988 case 'role':
989 if ( is_user_logged_in() ) {
990 $current_user = wp_get_current_user();
991
992 $user_roles = $current_user->roles;
993
994 if ( in_array( $rule['minor'], $user_roles, true ) ) {
995 $condition_result = true;
996 } else {
997 $condition_result = false;
998 }
999 } else {
1000 $condition_result = false;
1001 }
1002 break;
1003 case 'post_type':
1004 if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
1005 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
1006 } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
1007 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
1008 }
1009 break;
1010 case 'taxonomy':
1011 // All taxonomy pages.
1012 if ( ! $rule['minor'] ) {
1013 if ( is_archive() ) {
1014 if ( is_tag() || is_category() || is_tax() ) {
1015 $condition_result = true;
1016 }
1017 } elseif ( is_singular() ) {
1018 $post_taxonomies = get_post_taxonomies();
1019 $condition_result = ! empty( $post_taxonomies );
1020 }
1021 break;
1022 }
1023
1024 // Specified taxonomy page.
1025 $term = explode( '_tax_', $rule['minor'] ); // $term[0] is taxonomy name; $term[1] is term id.
1026 if ( isset( $term[0] ) && isset( $term[1] ) ) {
1027 $term[1] = self::maybe_get_split_term( $term[1], $term[0] );
1028 }
1029
1030 // All pages of the specified taxonomy.
1031 if ( ! isset( $term[1] ) || ! $term[1] ) {
1032 if ( is_tax( $term[0] ) ) {
1033 $condition_result = true;
1034 } elseif ( is_singular() ) {
1035 if ( in_array( $term[0], get_post_taxonomies(), true ) ) {
1036 $condition_result = true;
1037 }
1038 }
1039 break;
1040 }
1041
1042 // All pages with the specified taxonomy term.
1043 if ( is_tax( $term[0], $term[1] ) ) {
1044 $condition_result = true;
1045 } elseif ( is_singular() && has_term( $term[1], $term[0] ) ) {
1046 $condition_result = true;
1047 }
1048 break;
1049 }
1050
1051 if ( $condition_result || self::$passed_template_redirect ) {
1052 // Some of the conditions will return false when checked before the template_redirect
1053 // action has been called, like is_page(). Only store positive lookup results, which
1054 // won't be false positives, before template_redirect, and everything after.
1055 $condition_result_cache[ $condition_key ] = $condition_result;
1056 }
1057 }
1058
1059 if (
1060 isset( $conditions['match_all'] )
1061 && '1' === $conditions['match_all']
1062 && ! $condition_result
1063 ) {
1064
1065 // In case the match_all flag was set we quit on first failed condition.
1066 break;
1067 } elseif (
1068 (
1069 empty( $conditions['match_all'] )
1070 || '1' !== $conditions['match_all']
1071 )
1072 && $condition_result
1073 ) {
1074
1075 // Only quit on first condition if the match_all flag was not set.
1076 break;
1077 }
1078 }
1079
1080 if (
1081 (
1082 'show' === $conditions['action']
1083 && ! $condition_result
1084 ) || (
1085 'hide' === $conditions['action']
1086 && $condition_result
1087 )
1088 ) {
1089 return false;
1090 }
1091
1092 return true;
1093 }
1094
1095 /**
1096 * Helper function wrapping strcasecmp to compare term names.
1097 *
1098 * @param string $a str1.
1099 * @param string $b str2.
1100 */
1101 public static function strcasecmp_name( $a, $b ) {
1102 return strcasecmp( $a->name, $b->name );
1103 }
1104
1105 /**
1106 * Determine if provided term has been split.
1107 *
1108 * @param int $old_term_id Old term id to test.
1109 * @param string $taxonomy Taxonmy that $old_term_id belongs to.
1110 */
1111 public static function maybe_get_split_term( $old_term_id = '', $taxonomy = '' ) {
1112 $term_id = $old_term_id;
1113
1114 if ( 'tag' === $taxonomy ) {
1115 $taxonomy = 'post_tag';
1116 }
1117
1118 $new_term_id = wp_get_split_term( $old_term_id, $taxonomy );
1119 if ( $new_term_id ) {
1120 $term_id = $new_term_id;
1121 }
1122
1123 return $term_id;
1124 }
1125
1126 /**
1127 * Upgrade routine to go through all widgets and move the Post Type
1128 * setting to its newer location.
1129 *
1130 * @since 4.7.1
1131 */
1132 public static function migrate_post_type_rules() {
1133 global $wp_registered_widgets;
1134
1135 $sidebars_widgets = get_option( 'sidebars_widgets' );
1136
1137 // Going through all sidebars and through inactive and orphaned widgets.
1138 foreach ( $sidebars_widgets as $sidebar ) {
1139 if ( ! is_array( $sidebar ) ) {
1140 continue;
1141 }
1142
1143 foreach ( $sidebar as $widget ) {
1144 // $widget is the id of the widget
1145 if ( empty( $wp_registered_widgets[ $widget ] ) ) {
1146 continue;
1147 }
1148
1149 $opts = $wp_registered_widgets[ $widget ];
1150 $instances = get_option( $opts['callback'][0]->option_name );
1151
1152 if ( ! is_array( $instances ) || empty( $instances ) ) {
1153 continue;
1154 }
1155
1156 // Going through each instance of the widget.
1157 foreach ( $instances as $number => $instance ) {
1158 if (
1159 ! is_array( $instance ) ||
1160 empty( $instance['conditions'] ) ||
1161 empty( $instance['conditions']['rules'] )
1162 ) {
1163 continue;
1164 }
1165
1166 // Going through all visibility rules.
1167 foreach ( $instance['conditions']['rules'] as $index => $rule ) {
1168
1169 // We only need Post Type rules.
1170 if ( 'post_type' !== $rule['major'] ) {
1171 continue;
1172 }
1173
1174 $rule_type = false;
1175
1176 // Post type or type archive rule.
1177 if ( str_starts_with( $rule['minor'], 'post_type_archive' ) ) {
1178 $rule_type = 'post_type_archive';
1179 } elseif ( str_starts_with( $rule['minor'], 'post_type' ) ) {
1180 $rule_type = 'post_type';
1181 }
1182
1183 if ( $rule_type ) {
1184 $post_type = substr( $rule['minor'], strlen( $rule_type ) + 1 );
1185 $rule['minor'] = $rule_type . '-' . $post_type;
1186 $rule['major'] = 'page';
1187
1188 $instances[ $number ]['conditions']['rules'][ $index ] = $rule;
1189 }
1190 }
1191 }
1192
1193 update_option( $opts['callback'][0]->option_name, $instances );
1194 }
1195 }
1196 }
1197 }
1198
1199 add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
1200
1201 // Add the 'conditions' attribute to server side rendered blocks
1202 // 'init' happens too late to hook on block registration.
1203 global $pagenow;
1204 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
1205 if ( is_customize_preview()
1206 || 'widgets.php' === $pagenow
1207 || str_contains( $current_url, '/wp-json/wp/v2/block-renderer' )
1208 || 1 === preg_match( '~^/wp/v2/sites/\d+/block-renderer~', $current_url )
1209 ) {
1210 Jetpack_Widget_Conditions::add_block_attributes_filter();
1211 }
1212