PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 508 releases
← All changes | modules/widget-visibility/widget-conditions.php +1287 -0 16.2-beta → 16.3 View file →
@@ -1,0 +1,1287 @@
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\Block_Scanner;
9 +use Automattic\Jetpack\Assets;
10 +
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit( 0 );
13 +}
14 +
15 +/**
16 + * Hide or show legacy widgets conditionally.
17 + *
18 + * This class has two responsiblities - administrating the conditions in which legacy widgets may be hidden or shown
19 + * and hiding/showing the legacy widgets on the front-end of the site, depending upon the evaluation of those conditions.
20 + *
21 + * Administrating the conditions can be done in one of four different WordPress screens, plus direct use of the API and
22 + * is supplemented with a legacy widget preview screen. The four different admin screens are
23 + *
24 + * Gutenberg widget experience - widget admin (widgets.php + API + legacy widget preview)
25 + * Gutenberg widget experience - Customizer (customizer screen/API + API + legacy widget preview)
26 + * Classic widget experience - widget admin (widgets.php + admin-ajax XHR requests)
27 + * Classic widget experience - Customizer (customizer screen/API)
28 + *
29 + * 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/
30 + */
31 +class Jetpack_Widget_Conditions {
32 + /**
33 + * Stores condition for template_redirect action.
34 + *
35 + * @var bool
36 + */
37 + public static $passed_template_redirect = false;
38 +
39 + /**
40 + * Class initializer.
41 + */
42 + public static function init() {
43 + global $pagenow;
44 +
45 + // The Gutenberg based widget experience will show a preview of legacy widgets by including a URL beginning
46 + // widgets.php?legacy-widget-preview inside an iframe. Previews don't need widget editing loaded and also don't
47 + // want to run the filter - if the widget is filtered out it'll be empty, which would be confusing.
48 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
49 + if ( isset( $_GET['legacy-widget-preview'] ) ) {
50 + return;
51 + }
52 +
53 + // If action is posted and it's save-widget then it's relevant to widget conditions, otherwise it's something
54 + // else and it's not worth registering hooks.
55 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
56 + if ( isset( $_POST['action'] ) && ! isset( $_POST['customize_changeset_uuid'] ) && ! in_array( $_POST['action'], array( 'save-widget', 'update-widget' ), true ) ) {
57 + return;
58 + }
59 +
60 + // API call to *list* the widget types doesn't use editing visibility or display widgets.
61 + if ( isset( $_SERVER['REQUEST_URI'] ) && str_contains( $_SERVER['REQUEST_URI'], '/widget-types?' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
62 + return;
63 + }
64 +
65 + $add_data_assets_to_page = false;
66 + $add_html_to_form = false;
67 + $handle_widget_updates = false;
68 + $add_block_controls = false;
69 +
70 + // Check to see if using the customizer, but not using the preview. The preview should filter out widgets,
71 + // the customizer controls in the sidebar should not (so they can be edited).
72 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
73 + $customizer_not_previewer = is_customize_preview() && ! isset( $_GET['customize_changeset_uuid'] );
74 + $using_classic_experience = ! wp_use_widgets_block_editor();
75 + if ( $using_classic_experience &&
76 + ( $customizer_not_previewer || 'widgets.php' === $pagenow ||
77 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
78 + ( 'admin-ajax.php' === $pagenow && array_key_exists( 'action', $_POST ) && 'save-widget' === $_POST['action'] )
79 + )
80 + ) {
81 + $add_data_assets_to_page = true;
82 + $add_html_to_form = true;
83 + $handle_widget_updates = true;
84 + } else {
85 + // On a screen that is hosting the API in the gutenberg editing experience.
86 + if ( $customizer_not_previewer || 'widgets.php' === $pagenow ) {
87 + $add_data_assets_to_page = true;
88 + $add_block_controls = true;
89 + }
90 +
91 + // Encoding for a particular widget end point.
92 + if ( isset( $_SERVER['REQUEST_URI'] ) && 1 === preg_match( '|/widget-types/.*/encode|', $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
93 + $add_html_to_form = true;
94 + $handle_widget_updates = true;
95 + }
96 +
97 + // Batch API is usually saving but could be anything.
98 + $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
99 + if ( str_contains( $current_url, '/wp-json/batch/v1' ) || 1 === preg_match( '/^\/wp\/v2\/sites\/\d+\/batch\/v1/', $current_url ) ) {
100 + $handle_widget_updates = true;
101 + $add_html_to_form = true;
102 + }
103 +
104 + // Saving widgets via non-batch API. This isn't used within WordPress but could be used by third parties in theory.
105 + 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
106 + $handle_widget_updates = true;
107 + $add_html_to_form = true;
108 + }
109 + }
110 +
111 + if ( $add_html_to_form ) {
112 + add_action( 'in_widget_form', array( __CLASS__, 'widget_conditions_admin' ), 10, 3 );
113 + }
114 +
115 + if ( $handle_widget_updates ) {
116 + add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
117 + }
118 +
119 + if ( $add_data_assets_to_page ) {
120 + add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
121 + }
122 +
123 + if ( $add_block_controls ) {
124 + add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'setup_block_controls' ) );
125 + }
126 +
127 + if ( ! $add_html_to_form && ! $handle_widget_updates && ! $add_data_assets_to_page &&
128 + ! in_array( $pagenow, array( 'wp-login.php', 'wp-register.php' ), true )
129 + ) {
130 + // Not hit any known widget admin endpoint, register widget display hooks instead.
131 + add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
132 + add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
133 + add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ) );
134 + }
135 + }
136 +
137 + /**
138 + * Enqueue the block-based widget visibility scripts.
139 + */
140 + public static function setup_block_controls() {
141 + Assets::register_script(
142 + 'widget-visibility-editor',
143 + '_inc/build/widget-visibility/editor/index.js',
144 + JETPACK__PLUGIN_FILE,
145 + array(
146 + 'in_footer' => true,
147 + 'textdomain' => 'jetpack',
148 + )
149 + );
150 + Assets::enqueue_script( 'widget-visibility-editor' );
151 + }
152 +
153 + /**
154 + * Add the 'conditions' attribute, where visibility rules are stored, to some blocks.
155 + *
156 + * We normally add the block attributes in the browser's javascript env only,
157 + * but these blocks use a ServerSideRender dynamic preview, so the php env needs
158 + * to know about the new attribute, too.
159 + */
160 + public static function add_block_attributes_filter() {
161 + $blocks = array(
162 + // These use <ServerSideRender>.
163 + 'core/calendar',
164 + 'core/latest-comments',
165 + 'core/rss',
166 + 'core/archives',
167 + 'core/tag-cloud',
168 + 'core/page-list',
169 + 'core/latest-posts',
170 + 'woocommerce/product-categories',
171 + );
172 + /**
173 + * Filters the list of widget visibility blocks using <ServerSideRender>.
174 + *
175 + * @since 12.4
176 + *
177 + * @module widget-visibility
178 + *
179 + * @param string[] $blocks Array of block names from WordPress core and WooCommerce.
180 + */
181 + $blocks_to_add_visibility_conditions = apply_filters( 'jetpack_widget_visibility_server_side_render_blocks', $blocks );
182 +
183 + /**
184 + * Block registration filter callback.
185 + *
186 + * @param array $settings Array of arguments for registering a block type.
187 + * @param string $name Block type name including namespace.
188 + * @return array
189 + */
190 + $filter_metadata_registration = function ( $settings, $name ) use ( $blocks_to_add_visibility_conditions ) {
191 + if ( in_array( $name, $blocks_to_add_visibility_conditions, true ) && ! empty( $settings['attributes'] ) ) {
192 + $settings['attributes']['conditions'] = array(
193 + 'type' => 'object',
194 + );
195 + }
196 + return $settings;
197 + };
198 +
199 + add_filter( 'register_block_type_args', $filter_metadata_registration, 10, 2 );
200 + }
201 +
202 + /**
203 + * Prepare the interface for editing widgets - loading css, javascript & data
204 + */
205 + public static function widget_admin_setup() {
206 + wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ), array( 'widgets' ), JETPACK__VERSION );
207 + wp_style_add_data( 'widget-conditions', 'rtl', 'replace' );
208 + wp_enqueue_script(
209 + 'widget-conditions',
210 + Assets::get_file_url_for_environment(
211 + '_inc/build/widget-visibility/widget-conditions/widget-conditions.min.js',
212 + 'modules/widget-visibility/widget-conditions/widget-conditions.js'
213 + ),
214 + array( 'jquery', 'jquery-ui-core' ),
215 + JETPACK__VERSION,
216 + true
217 + );
218 +
219 + // Set up a single copy of all of the data that Widget Visibility needs.
220 + // This allows all widget conditions to reuse the same data, keeping page size down
221 + // and eliminating the AJAX calls we used to have to use to fetch the minor rule options.
222 + $widget_conditions_data = array();
223 +
224 + $widget_conditions_data['category'] = array();
225 + $widget_conditions_data['category'][] = array( '', __( 'All category pages', 'jetpack' ) );
226 +
227 + $categories = get_categories(
228 + array(
229 + /**
230 + * Specific a maximum number of categories to query for the Widget visibility UI.
231 + *
232 + * @module widget-visibility
233 + *
234 + * @since 9.1.0
235 + *
236 + * @param int $number Maximum number of categories displayed in the Widget visibility UI.
237 + */
238 + 'number' => (int) apply_filters( 'jetpack_widget_visibility_max_number_categories', 1000 ),
239 + 'orderby' => 'count',
240 + 'order' => 'DESC',
241 + )
242 + );
243 + usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
244 +
245 + foreach ( $categories as $category ) {
246 + $widget_conditions_data['category'][] = array( (string) $category->term_id, $category->name );
247 + }
248 +
249 + $widget_conditions_data['loggedin'] = array();
250 + $widget_conditions_data['loggedin'][] = array( 'loggedin', __( 'Logged In', 'jetpack' ) );
251 + $widget_conditions_data['loggedin'][] = array( 'loggedout', __( 'Logged Out', 'jetpack' ) );
252 +
253 + $widget_conditions_data['author'] = array();
254 + $widget_conditions_data['author'][] = array( '', __( 'All author pages', 'jetpack' ) );
255 +
256 + /*
257 + * Query for users with publish caps.
258 + */
259 + $authors_args = array(
260 + 'orderby' => 'name',
261 + 'capability' => array( 'edit_posts' ),
262 + 'fields' => array( 'ID', 'display_name' ),
263 + );
264 +
265 + $authors = get_users( $authors_args );
266 +
267 + foreach ( $authors as $author ) {
268 + $widget_conditions_data['author'][] = array( (string) $author->ID, $author->display_name );
269 + }
270 +
271 + $widget_conditions_data['role'] = array();
272 +
273 + global $wp_roles;
274 +
275 + foreach ( $wp_roles->roles as $role_key => $role ) {
276 + $widget_conditions_data['role'][] = array( (string) $role_key, $role['name'] );
277 + }
278 +
279 + $widget_conditions_data['tag'] = array();
280 + $widget_conditions_data['tag'][] = array( '', __( 'All tag pages', 'jetpack' ) );
281 +
282 + $tags = get_tags(
283 + array(
284 + /**
285 + * Specific a maximum number of tags to query for the Widget visibility UI.
286 + *
287 + * @module widget-visibility
288 + *
289 + * @since 9.1.0
290 + *
291 + * @param int $number Maximum number of tags displayed in the Widget visibility UI.
292 + */
293 + 'number' => (int) apply_filters( 'jetpack_widget_visibility_max_number_tags', 1000 ),
294 + 'orderby' => 'count',
295 + 'order' => 'DESC',
296 + )
297 + );
298 + usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
299 +
300 + foreach ( $tags as $tag ) {
301 + $widget_conditions_data['tag'][] = array( (string) $tag->term_id, $tag->name );
302 + }
303 +
304 + $widget_conditions_data['date'] = array();
305 + $widget_conditions_data['date'][] = array( '', __( 'All date archives', 'jetpack' ) );
306 + $widget_conditions_data['date'][] = array( 'day', __( 'Daily archives', 'jetpack' ) );
307 + $widget_conditions_data['date'][] = array( 'month', __( 'Monthly archives', 'jetpack' ) );
308 + $widget_conditions_data['date'][] = array( 'year', __( 'Yearly archives', 'jetpack' ) );
309 +
310 + $widget_conditions_data['page'] = array();
311 + $widget_conditions_data['page'][] = array( 'front', __( 'Front page', 'jetpack' ) );
312 + $widget_conditions_data['page'][] = array( 'posts', __( 'Posts page', 'jetpack' ) );
313 + $widget_conditions_data['page'][] = array( 'archive', __( 'Archive page', 'jetpack' ) );
314 + $widget_conditions_data['page'][] = array( '404', __( '404 error page', 'jetpack' ) );
315 + $widget_conditions_data['page'][] = array( 'search', __( 'Search results', 'jetpack' ) );
316 +
317 + $post_types = get_post_types( array( 'public' => true ), 'objects' );
318 +
319 + $widget_conditions_post_types = array();
320 + $widget_conditions_post_type_archives = array();
321 +
322 + foreach ( $post_types as $post_type ) {
323 + $widget_conditions_post_types[] = array( 'post_type-' . $post_type->name, $post_type->labels->singular_name );
324 + $widget_conditions_post_type_archives[] = array( 'post_type_archive-' . $post_type->name, $post_type->labels->name );
325 + }
326 +
327 + $widget_conditions_data['page'][] = array( __( 'Post type:', 'jetpack' ), $widget_conditions_post_types );
328 +
329 + $widget_conditions_data['page'][] = array( __( 'Post type Archives:', 'jetpack' ), $widget_conditions_post_type_archives );
330 +
331 + $pages = self::get_pages();
332 +
333 + $dropdown_tree_args = array(
334 + 'depth' => 0,
335 + 'child_of' => 0,
336 + 'selected' => 0,
337 + 'echo' => false,
338 + 'name' => 'page_id',
339 + 'id' => '',
340 + 'class' => '',
341 + 'show_option_none' => '',
342 + 'show_option_no_change' => '',
343 + 'option_none_value' => '',
344 + 'value_field' => 'ID',
345 + );
346 + $pages_dropdown = walk_page_dropdown_tree( $pages, 0, $dropdown_tree_args );
347 + preg_match_all( '/value=.([0-9]+).[^>]*>([^<]+)</', $pages_dropdown, $page_ids_and_titles, PREG_SET_ORDER );
348 + $static_pages = array();
349 +
350 + foreach ( $page_ids_and_titles as $page_id_and_title ) {
351 + $static_pages[] = array( (string) $page_id_and_title[1], $page_id_and_title[2] );
352 + }
353 +
354 + $widget_conditions_data['page'][] = array( __( 'Static page:', 'jetpack' ), $static_pages );
355 +
356 + $widget_conditions_data['taxonomy'] = array();
357 + $widget_conditions_data['taxonomy'][] = array( '', __( 'All taxonomy pages', 'jetpack' ) );
358 +
359 + $taxonomies = get_taxonomies(
360 + /**
361 + * Filters args passed to get_taxonomies.
362 + *
363 + * @see https://developer.wordpress.org/reference/functions/get_taxonomies/
364 + *
365 + * @since 5.3.0
366 + *
367 + * @module widget-visibility
368 + *
369 + * @param array $args Widget Visibility taxonomy arguments.
370 + */
371 + apply_filters( 'jetpack_widget_visibility_tax_args', array( '_builtin' => false ) ),
372 + 'objects'
373 + );
374 +
375 + usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
376 +
377 + foreach ( $taxonomies as $taxonomy ) {
378 + $taxonomy_terms = get_terms(
379 + array( $taxonomy->name ),
380 + array(
381 + 'number' => 250,
382 + 'hide_empty' => false,
383 + )
384 + );
385 +
386 + $widget_conditions_terms = array();
387 + $widget_conditions_terms[] = array( $taxonomy->name, $taxonomy->labels->all_items );
388 +
389 + foreach ( $taxonomy_terms as $term ) {
390 + $widget_conditions_terms[] = array( $taxonomy->name . '_tax_' . $term->term_id, $term->name );
391 + }
392 +
393 + $widget_conditions_data['taxonomy'][] = array( $taxonomy->labels->name . ':', $widget_conditions_terms );
394 + }
395 +
396 + wp_localize_script( 'widget-conditions', 'widget_conditions_data', $widget_conditions_data );
397 +
398 + // Save a list of the IDs of all pages that have children for dynamically showing the "Include children" checkbox.
399 + $all_pages = self::get_pages();
400 + $all_parents = array();
401 +
402 + foreach ( $all_pages as $page ) {
403 + if ( $page->post_parent ) {
404 + $all_parents[ (string) $page->post_parent ] = true;
405 + }
406 + }
407 +
408 + $front_page_id = get_option( 'page_on_front' );
409 +
410 + if ( isset( $all_parents[ $front_page_id ] ) ) {
411 + $all_parents['front'] = true;
412 + }
413 +
414 + wp_localize_script( 'widget-conditions', 'widget_conditions_parent_pages', $all_parents );
415 + }
416 +
417 + /**
418 + * Retrieves a full list of all pages, containing just the IDs, post_parent, post_title, and post_status fields.
419 + *
420 + * Since the WordPress' `get_pages` function does not allow us to fetch only the fields mentioned
421 + * above, we need to introduce a custom method using a direct SQL query fetching those.
422 + *
423 + * By fetching only those four fields and not populating the object cache for all the pages, we can
424 + * improve the performance of the query on sites having a lot of pages.
425 + *
426 + * @see https://core.trac.wordpress.org/ticket/51469
427 + *
428 + * @return array List of all pages on the site (stdClass objects containing ID, post_title, post_parent, and post_status only).
429 + */
430 + public static function get_pages() {
431 + global $wpdb;
432 +
433 + $last_changed = wp_cache_get_last_changed( 'posts' );
434 + $cache_key = "get_pages:$last_changed";
435 + $pages = wp_cache_get( $cache_key, 'widget_conditions' );
436 + if ( false === $pages ) {
437 + $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" );
438 + wp_cache_set( $cache_key, $pages, 'widget_conditions' );
439 + }
440 +
441 + // Copy-pasted from the get_pages function. For usage in the `widget_conditions_get_pages` filter.
442 + $parsed_args = array(
443 + 'child_of' => 0,
444 + 'sort_order' => 'ASC',
445 + 'sort_column' => 'post_title',
446 + 'hierarchical' => 1,
447 + 'exclude' => array(),
448 + 'include' => array(),
449 + 'meta_key' => '',
450 + 'meta_value' => '',
451 + 'authors' => '',
452 + 'parent' => -1,
453 + 'exclude_tree' => array(),
454 + 'number' => '',
455 + 'offset' => 0,
456 + 'post_type' => 'page',
457 + 'post_status' => 'publish',
458 + );
459 +
460 + /**
461 + * Filters the retrieved list of pages.
462 + *
463 + * @since 9.1.0
464 + *
465 + * @module widget-visibility
466 + *
467 + * @param stdClass[] $pages Array of objects containing only the ID, post_parent, post_title, and post_status fields.
468 + * @param array $parsed_args Array of get_pages() arguments.
469 + */
470 + return apply_filters( 'jetpack_widget_visibility_get_pages', $pages, $parsed_args );
471 + }
472 +
473 + /**
474 + * Add the widget conditions to each widget in the admin.
475 + *
476 + * @param WP_Widget $widget Widget to add conditions settings to.
477 + * @param null $return unused.
478 + * @param array $instance The widget settings.
479 + */
480 + public static function widget_conditions_admin( $widget, $return, $instance ) {
481 + $conditions = array();
482 +
483 + if ( isset( $instance['conditions'] ) ) {
484 + $conditions = $instance['conditions'];
485 + }
486 +
487 + if ( ! isset( $conditions['action'] ) ) {
488 + $conditions['action'] = 'show';
489 + }
490 +
491 + if ( empty( $conditions['rules'] ) ) {
492 + $conditions['rules'][] = array(
493 + 'major' => '',
494 + 'minor' => '',
495 + 'has_children' => '',
496 + );
497 + }
498 +
499 + if ( empty( $conditions['match_all'] ) ) {
500 + $conditions['match_all'] = false;
501 + }
502 +
503 + ?>
504 + <div
505 + class="
506 + widget-conditional
507 + <?php
508 + // $_POST['widget-conditions-visible'] is used in the classic widget experience to decide whether to
509 + // display the visibility panel open, e.g. when saving. In the gutenberg widget experience the POST
510 + // value will always be empty, but this is fine - it doesn't rerender the HTML when saving anyway.
511 + if (
512 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
513 + empty( $_POST['widget-conditions-visible'] ) || '0' === $_POST['widget-conditions-visible']
514 + ) {
515 + ?>
516 + widget-conditional-hide
517 + <?php
518 + }
519 + ?>
520 + <?php
521 + if ( ! empty( $conditions['match_all'] ) && $conditions['match_all'] ) {
522 + ?>
523 + intersection
524 + <?php
525 + } else {
526 + ?>
527 + conjunction
528 + <?php
529 + }
530 + ?>
531 + ">
532 + <input type="hidden" name="widget-conditions-visible" value="
533 + <?php
534 + if ( isset( $_POST['widget-conditions-visible'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
535 + echo esc_attr( filter_var( wp_unslash( $_POST['widget-conditions-visible'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
536 + } else {
537 + echo 0;
538 + }
539 + ?>
540 + " />
541 + <?php
542 + if ( ! isset( $_POST['widget-conditions-visible'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
543 + ?>
544 + <a href="#" class="button display-options"><?php esc_html_e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
545 + <div class="widget-conditional-inner">
546 + <div class="condition-top">
547 + <?php
548 + printf(
549 + // 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'.
550 + esc_html_x( '%s if:', 'placeholder: dropdown menu to select widget visibility; hide if or show if', 'jetpack' ),
551 + '<select name="' . esc_attr( $widget->get_field_name( 'conditions[action]' ) ) . '">
552 + <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>
553 + <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>
554 + </select>'
555 + );
556 + ?>
557 + </div><!-- .condition-top -->
558 +
559 + <div class="conditions">
560 + <?php
561 +
562 + foreach ( $conditions['rules'] as $rule_index => $rule ) {
563 + $rule = wp_parse_args(
564 + $rule,
565 + array(
566 + 'major' => '',
567 + 'minor' => '',
568 + 'has_children' => '',
569 + )
570 + );
571 + ?>
572 + <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'] ); ?>">
573 + <div class="selection alignleft">
574 + <select class="conditions-rule-major" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_major][]' ) ); ?>">
575 + <option value="" <?php selected( '', $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
576 + <option value="category" <?php selected( 'category', $rule['major'] ); ?>><?php esc_html_e( 'Category', 'jetpack' ); ?></option>
577 + <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>
578 +
579 + <?php if ( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { // this doesn't work on .com because of caching. ?>
580 + <option value="loggedin" <?php selected( 'loggedin', $rule['major'] ); ?>><?php echo esc_html_x( 'User', 'Noun', 'jetpack' ); ?></option>
581 + <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>
582 + <?php } ?>
583 +
584 + <option value="tag" <?php selected( 'tag', $rule['major'] ); ?>><?php echo esc_html_x( 'Tag', 'Noun, as in: "This post has one tag."', 'jetpack' ); ?></option>
585 + <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>
586 + <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>
587 + <?php if ( get_taxonomies( array( '_builtin' => false ) ) ) : ?>
588 + <option value="taxonomy" <?php selected( 'taxonomy', $rule['major'] ); ?>><?php echo esc_html_x( 'Taxonomy', 'Noun, as in: "This post has one taxonomy."', 'jetpack' ); ?></option>
589 + <?php endif; ?>
590 + </select>
591 +
592 + <?php echo esc_html_x( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>
593 +
594 + <select class="conditions-rule-minor" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_minor][]' ) ); ?>"
595 + <?php
596 + if ( ! $rule['major'] ) {
597 + echo ' disabled="disabled"';
598 + }
599 + ?>
600 + >
601 + <?php
602 + /*
603 + Include the currently selected value so that if the widget is saved without
604 + expanding the Visibility section, we don't lose the minor part of the rule.
605 + If it is opened, this list is cleared out and populated with all the values.
606 + */
607 + ?>
608 + <option value="<?php echo esc_attr( $rule['minor'] ); ?>" selected="selected"></option>
609 + </select>
610 +
611 + <span class="conditions-rule-has-children"
612 + <?php
613 + if ( ! $rule['has_children'] ) {
614 + echo ' style="display: none;"';
615 + }
616 + ?>
617 + >
618 + <label>
619 + <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 ); ?> />
620 + <?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' ); ?>
621 + </label>
622 + </span>
623 + </div>
624 +
625 + <div class="condition-control">
626 + <span class="condition-conjunction">
627 + <?php echo esc_html_x( 'or', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
628 + </span>
629 + <span class="condition-intersection">
630 + <?php echo esc_html_x( 'and', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
631 + </span>
632 + <div class="actions alignright">
633 + <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>
634 + </div>
635 + </div>
636 +
637 + </div><!-- .condition -->
638 + <?php
639 + }
640 +
641 + ?>
642 + </div><!-- .conditions -->
643 + <div class="conditions">
644 + <div class="condition-top">
645 + <label>
646 + <input
647 + type="checkbox"
648 + name="<?php echo esc_attr( $widget->get_field_name( 'conditions[match_all]' ) ); ?>"
649 + value="1"
650 + class="conditions-match-all"
651 + <?php checked( $conditions['match_all'], '1' ); ?> />
652 + <?php esc_html_e( 'Match all conditions', 'jetpack' ); ?>
653 + </label>
654 + </div><!-- .condition-top -->
655 + </div><!-- .conditions -->
656 + </div><!-- .widget-conditional-inner -->
657 + </div><!-- .widget-conditional -->
658 + <?php
659 + }
660 +
661 + /**
662 + * On an AJAX update of the widget settings, process the display conditions.
663 + *
664 + * @param array $instance The current instance's settings.
665 + * @param array $new_instance New settings for this instance as input by the user.
666 + * @param array $old_instance Old settings for this instance.
667 + * @return array Modified settings.
668 + */
669 + public static function widget_update( $instance, $new_instance, $old_instance ) {
670 + $conditions = array();
671 + $conditions['action'] = $new_instance['conditions']['action'] ?? null;
672 + $conditions['match_all'] = ! empty( $new_instance['conditions']['match_all'] ) ? '1' : '0';
673 + $conditions['rules'] = $new_instance['conditions']['rules'] ?? array();
674 +
675 + if ( isset( $new_instance['conditions']['rules_major'] ) ) {
676 + foreach ( $new_instance['conditions']['rules_major'] as $index => $major_rule ) {
677 + if ( ! $major_rule ) {
678 + continue;
679 + }
680 +
681 + $conditions['rules'][] = array(
682 + 'major' => $major_rule,
683 + 'minor' => $new_instance['conditions']['rules_minor'][ $index ] ?? '',
684 + 'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ),
685 + );
686 + }
687 + }
688 +
689 + if ( ! empty( $conditions['rules'] ) ) {
690 + $instance['conditions'] = $conditions;
691 + } elseif ( empty( $new_instance['conditions']['rules'] ) ) {
692 + unset( $instance['conditions'] );
693 + }
694 +
695 + if (
696 + ( isset( $instance['conditions'] ) && ! isset( $old_instance['conditions'] ) )
697 + ||
698 + (
699 + isset( $instance['conditions'], $old_instance['conditions'] )
700 + &&
701 + serialize( $instance['conditions'] ) !== serialize( $old_instance['conditions'] ) // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
702 + )
703 + ) {
704 +
705 + /**
706 + * Fires after the widget visibility conditions are saved.
707 + *
708 + * @module widget-visibility
709 + *
710 + * @since 2.4.0
711 + */
712 + do_action( 'widget_conditions_save' );
713 + } elseif ( ! isset( $instance['conditions'] ) && isset( $old_instance['conditions'] ) ) {
714 +
715 + /**
716 + * Fires after the widget visibility conditions are deleted.
717 + *
718 + * @module widget-visibility
719 + *
720 + * @since 2.4.0
721 + */
722 + do_action( 'widget_conditions_delete' );
723 + }
724 +
725 + return $instance;
726 + }
727 +
728 + /**
729 + * Filter the list of widgets for a sidebar so that active sidebars work as expected.
730 + *
731 + * @param array $widget_areas An array of widget areas and their widgets.
732 + * @return array The modified $widget_area array.
733 + */
734 + public static function sidebars_widgets( $widget_areas ) {
735 + $settings = array();
736 +
737 + if ( ! is_array( $widget_areas ) ) {
738 + return $widget_areas;
739 + }
740 +
741 + foreach ( $widget_areas as $widget_area => $widgets ) {
742 + if ( empty( $widgets ) ) {
743 + continue;
744 + }
745 +
746 + if ( ! is_array( $widgets ) ) {
747 + continue;
748 + }
749 +
750 + if ( 'wp_inactive_widgets' === $widget_area ) {
751 + continue;
752 + }
753 +
754 + foreach ( $widgets as $position => $widget_id ) {
755 + // Find the conditions for this widget.
756 + if ( preg_match( '/^(.+?)-(\d+)$/', $widget_id, $matches ) ) {
757 + $id_base = $matches[1];
758 + $widget_number = (int) $matches[2];
759 + } else {
760 + $id_base = $widget_id;
761 + $widget_number = null;
762 + }
763 +
764 + if ( ! isset( $settings[ $id_base ] ) ) {
765 + $settings[ $id_base ] = get_option( 'widget_' . $id_base );
766 + }
767 +
768 + // New multi widget (WP_Widget).
769 + if ( $widget_number !== null ) {
770 + if ( isset( $settings[ $id_base ][ $widget_number ] ) && false === self::filter_widget( $settings[ $id_base ][ $widget_number ] ) ) {
771 + unset( $widget_areas[ $widget_area ][ $position ] );
772 + }
773 + } elseif ( ! empty( $settings[ $id_base ] ) && false === self::filter_widget( $settings[ $id_base ] ) ) { // Old single widget.
774 + unset( $widget_areas[ $widget_area ][ $position ] );
775 + }
776 + }
777 + }
778 +
779 + return $widget_areas;
780 + }
781 +
782 + /**
783 + * Set field $passed_template_redirect to true.
784 + */
785 + public static function template_redirect() {
786 + self::$passed_template_redirect = true;
787 + }
788 +
789 + /**
790 + * Generates a condition key based on the rule array.
791 + *
792 + * @param array $rule rule data.
793 + * @return string key used to retrieve the condition.
794 + */
795 + public static function generate_condition_key( $rule ) {
796 + if ( isset( $rule['has_children'] ) ) {
797 + return $rule['major'] . ':' . $rule['minor'] . ':' . $rule['has_children'];
798 + }
799 + return $rule['major'] . ':' . $rule['minor'];
800 + }
801 +
802 + /**
803 + * Normalize widget `content` into a string suitable for block scanning.
804 + *
805 + * @since 15.1
806 + *
807 + * @param mixed $content The widget instance 'content' value.
808 + * @return string|false Normalized string content or false if none.
809 + */
810 + private static function normalize_widget_content( $content ) {
811 + if ( empty( $content ) ) {
812 + return false;
813 + }
814 +
815 + if ( is_string( $content ) ) {
816 + return $content;
817 + }
818 +
819 + if ( ! is_array( $content ) ) {
820 + return false;
821 + }
822 +
823 + if ( isset( $content['content'] ) && is_string( $content['content'] ) ) {
824 + return $content['content'];
825 + }
826 +
827 + if ( isset( $content[0] ) && is_array( $content[0] ) && isset( $content[0]['blockName'] ) ) {
828 + // Looks like a parsed blocks array.
829 + return serialize_blocks( $content );
830 + }
831 +
832 + // Unknown array shape: treat as no visibility rules.
833 + return false;
834 + }
835 +
836 + /**
837 + * Determine whether the widget should be displayed based on conditions set by the user.
838 + *
839 + * @param array $instance The widget settings.
840 + * @return array Settings to display or bool false to hide.
841 + */
842 + public static function filter_widget( $instance ) {
843 + // Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
844 + // filtered out and become impossible to edit.
845 + 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/' ) ) {
846 + return $instance;
847 + }
848 + // WordPress.com specific check - here, referer ends in /rest-proxy/ and doesn't tell us what's requesting.
849 + $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
850 + $nonce = ! empty( $_REQUEST['_gutenberg_nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_gutenberg_nonce'] ) ) : '';
851 + $context = ! empty( $_REQUEST['context'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['context'] ) ) : '';
852 + if ( wp_verify_nonce( $nonce, 'gutenberg_request' ) &&
853 + 1 === preg_match( '~^/wp/v2/sites/\d+/(sidebars|widgets)~', $current_url ) && 'edit' === $context ) {
854 + return $instance;
855 + }
856 +
857 + if ( ! empty( $instance['conditions']['rules'] ) ) {
858 + // Legacy widgets: visibility found.
859 + if ( self::filter_widget_check_conditions( $instance['conditions'] ) ) {
860 + return $instance;
861 + }
862 + return false;
863 + }
864 +
865 + if ( empty( $instance['content'] ) ) {
866 + return $instance;
867 + }
868 + $content = self::normalize_widget_content( $instance['content'] ?? null );
869 +
870 + if ( false === $content || ! has_blocks( $content ) ) {
871 + // No visibility found.
872 + return $instance;
873 + }
874 +
875 + $scanner = Block_Scanner::create( $content );
876 + if ( ! $scanner ) {
877 + // No Rules: Display widget.
878 + return $instance;
879 + }
880 +
881 + // Find the first block that opens
882 + while ( $scanner->next_delimiter() ) {
883 + if ( ! $scanner->opens_block() ) {
884 + continue;
885 + }
886 +
887 + $attributes = $scanner->allocate_and_return_parsed_attributes();
888 +
889 + if ( ! is_array( $attributes ) || empty( $attributes['conditions']['rules'] ) ) {
890 + // No Rules: Display widget.
891 + return $instance;
892 + }
893 +
894 + if ( self::filter_widget_check_conditions( $attributes['conditions'] ) ) {
895 + // Rules passed checks: Display widget.
896 + return $instance;
897 + }
898 +
899 + // Rules failed checks: Hide widget.
900 + return false;
901 + }
902 + // No visibility found.
903 + return $instance;
904 + }
905 +
906 + /**
907 + * Determine whether the widget should be displayed based on conditions set by the user.
908 + *
909 + * @param array $conditions Visibility Conditions: An array with keys 'rules', 'action', and 'match_all'.
910 + * @return bool If the widget controlled by these conditions should show.
911 + */
912 + public static function filter_widget_check_conditions( $conditions ) {
913 + global $wp_query;
914 + // Store the results of all in-page condition lookups so that multiple widgets with
915 + // the same visibility conditions don't result in duplicate DB queries.
916 + static $condition_result_cache = array();
917 +
918 + $condition_result = false;
919 +
920 + foreach ( $conditions['rules'] as $rule ) {
921 + $condition_result = false;
922 + $condition_key = self::generate_condition_key( $rule );
923 +
924 + if ( isset( $condition_result_cache[ $condition_key ] ) ) {
925 + $condition_result = $condition_result_cache[ $condition_key ];
926 + } else {
927 + switch ( $rule['major'] ) {
928 + case 'date':
929 + switch ( $rule['minor'] ) {
930 + case '':
931 + $condition_result = is_date();
932 + break;
933 + case 'month':
934 + $condition_result = is_month();
935 + break;
936 + case 'day':
937 + $condition_result = is_day();
938 + break;
939 + case 'year':
940 + $condition_result = is_year();
941 + break;
942 + }
943 + break;
944 + case 'page':
945 + // Previously hardcoded post type options.
946 + if ( 'post' === $rule['minor'] ) {
947 + $rule['minor'] = 'post_type-post';
948 + } elseif ( ! $rule['minor'] ) {
949 + $rule['minor'] = 'post_type-page';
950 + }
951 +
952 + switch ( $rule['minor'] ) {
953 + case '404':
954 + $condition_result = is_404();
955 + break;
956 + case 'search':
957 + $condition_result = is_search();
958 + break;
959 + case 'archive':
960 + $condition_result = is_archive();
961 + break;
962 + case 'posts':
963 + $condition_result = $wp_query->is_posts_page;
964 + break;
965 + case 'home':
966 + $condition_result = is_home();
967 + break;
968 + case 'front':
969 + if ( current_theme_supports( 'infinite-scroll' ) ) {
970 + $condition_result = is_front_page();
971 + } else {
972 + $condition_result = is_front_page() && ! is_paged();
973 + }
974 + break;
975 + default:
976 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
977 + $condition_result = is_singular( substr( $rule['minor'], 10 ) );
978 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
979 + $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
980 + } elseif ( get_option( 'page_for_posts' ) === $rule['minor'] ) {
981 + // If $rule['minor'] is a page ID which is also the posts page.
982 + $condition_result = $wp_query->is_posts_page;
983 + } else {
984 + // $rule['minor'] is a page ID
985 + $condition_result = is_page() && ( get_the_ID() === (int) $rule['minor'] );
986 +
987 + // Check if $rule['minor'] is parent of page ID.
988 + if ( ! $condition_result && isset( $rule['has_children'] ) && $rule['has_children'] ) {
989 + $condition_result = wp_get_post_parent_id( get_the_ID() ) === (int) $rule['minor'];
990 + }
991 + }
992 + break;
993 + }
994 + break;
995 + case 'tag':
996 + // All tag pages.
997 + if ( ! $rule['minor'] ) {
998 + if ( is_tag() ) {
999 + $condition_result = true;
1000 + } elseif ( is_singular() ) {
1001 + if ( in_array( 'post_tag', get_post_taxonomies(), true ) ) {
1002 + $condition_result = true;
1003 + }
1004 + }
1005 + break;
1006 + }
1007 +
1008 + // All pages with the specified tag term.
1009 + if ( is_tag( $rule['minor'] ) ) {
1010 + $condition_result = true;
1011 + } elseif ( is_singular() && has_term( $rule['minor'], 'post_tag' ) ) {
1012 + $condition_result = true;
1013 + }
1014 + break;
1015 + case 'category':
1016 + // All category pages.
1017 + if ( ! $rule['minor'] ) {
1018 + if ( is_category() ) {
1019 + $condition_result = true;
1020 + } elseif ( is_singular() ) {
1021 + if ( in_array( 'category', get_post_taxonomies(), true ) ) {
1022 + $condition_result = true;
1023 + }
1024 + }
1025 + break;
1026 + }
1027 +
1028 + // All pages with the specified category term.
1029 + if ( is_category( $rule['minor'] ) ) {
1030 + $condition_result = true;
1031 + } elseif ( is_singular() && has_term( $rule['minor'], 'category' ) ) {
1032 + $condition_result = true;
1033 + }
1034 + break;
1035 + case 'loggedin':
1036 + $condition_result = is_user_logged_in();
1037 + if ( 'loggedin' !== $rule['minor'] ) {
1038 + $condition_result = ! $condition_result;
1039 + }
1040 + break;
1041 + case 'author':
1042 + if ( ! $rule['minor'] && is_author() ) {
1043 + $condition_result = true;
1044 + } elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
1045 + $condition_result = true;
1046 + } elseif ( is_singular() && $rule['minor'] ) {
1047 + $post = get_post();
1048 + if ( $post && $rule['minor'] === $post->post_author ) {
1049 + $condition_result = true;
1050 + }
1051 + }
1052 + break;
1053 + case 'role':
1054 + if ( is_user_logged_in() ) {
1055 + $current_user = wp_get_current_user();
1056 +
1057 + $user_roles = $current_user->roles;
1058 +
1059 + if ( in_array( $rule['minor'], $user_roles, true ) ) {
1060 + $condition_result = true;
1061 + } else {
1062 + $condition_result = false;
1063 + }
1064 + } else {
1065 + $condition_result = false;
1066 + }
1067 + break;
1068 + case 'post_type':
1069 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
1070 + $condition_result = is_singular( substr( $rule['minor'], 10 ) );
1071 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
1072 + $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
1073 + }
1074 + break;
1075 + case 'taxonomy':
1076 + // All taxonomy pages.
1077 + if ( ! $rule['minor'] ) {
1078 + if ( is_archive() ) {
1079 + if ( is_tag() || is_category() || is_tax() ) {
1080 + $condition_result = true;
1081 + }
1082 + } elseif ( is_singular() ) {
1083 + $post_taxonomies = get_post_taxonomies();
1084 + $condition_result = ! empty( $post_taxonomies );
1085 + }
1086 + break;
1087 + }
1088 +
1089 + // Specified taxonomy page.
1090 + $term = explode( '_tax_', $rule['minor'] ); // $term[0] is taxonomy name; $term[1] is term id.
1091 + if ( isset( $term[0] ) && isset( $term[1] ) ) {
1092 + $term[1] = self::maybe_get_split_term( $term[1], $term[0] );
1093 + }
1094 +
1095 + // All pages of the specified taxonomy.
1096 + if ( ! isset( $term[1] ) || ! $term[1] ) {
1097 + if ( is_tax( $term[0] ) ) {
1098 + $condition_result = true;
1099 + } elseif ( is_singular() ) {
1100 + if ( in_array( $term[0], get_post_taxonomies(), true ) ) {
1101 + $condition_result = true;
1102 + }
1103 + }
1104 + break;
1105 + }
1106 +
1107 + // All pages with the specified taxonomy term.
1108 + if ( is_tax( $term[0], $term[1] ) ) {
1109 + $condition_result = true;
1110 + } elseif ( is_singular() && has_term( $term[1], $term[0] ) ) {
1111 + $condition_result = true;
1112 + }
1113 + break;
1114 + }
1115 +
1116 + if ( $condition_result || self::$passed_template_redirect ) {
1117 + // Some of the conditions will return false when checked before the template_redirect
1118 + // action has been called, like is_page(). Only store positive lookup results, which
1119 + // won't be false positives, before template_redirect, and everything after.
1120 + $condition_result_cache[ $condition_key ] = $condition_result;
1121 + }
1122 + }
1123 +
1124 + if (
1125 + isset( $conditions['match_all'] )
1126 + && '1' === $conditions['match_all']
1127 + && ! $condition_result
1128 + ) {
1129 +
1130 + // In case the match_all flag was set we quit on first failed condition.
1131 + break;
1132 + } elseif (
1133 + (
1134 + empty( $conditions['match_all'] )
1135 + || '1' !== $conditions['match_all']
1136 + )
1137 + && $condition_result
1138 + ) {
1139 +
1140 + // Only quit on first condition if the match_all flag was not set.
1141 + break;
1142 + }
1143 + }
1144 +
1145 + if (
1146 + (
1147 + 'show' === $conditions['action']
1148 + && ! $condition_result
1149 + ) || (
1150 + 'hide' === $conditions['action']
1151 + && $condition_result
1152 + )
1153 + ) {
1154 + return false;
1155 + }
1156 +
1157 + return true;
1158 + }
1159 +
1160 + /**
1161 + * Helper function wrapping strcasecmp to compare term names.
1162 + *
1163 + * @param string $a str1.
1164 + * @param string $b str2.
1165 + */
1166 + public static function strcasecmp_name( $a, $b ) {
1167 + return strcasecmp( $a->name, $b->name );
1168 + }
1169 +
1170 + /**
1171 + * Determine if provided term has been split.
1172 + *
1173 + * @param int $old_term_id Old term id to test.
1174 + * @param string $taxonomy Taxonmy that $old_term_id belongs to.
1175 + */
1176 + public static function maybe_get_split_term( $old_term_id = '', $taxonomy = '' ) {
1177 + $term_id = $old_term_id;
1178 +
1179 + if ( 'tag' === $taxonomy ) {
1180 + $taxonomy = 'post_tag';
1181 + }
1182 +
1183 + $new_term_id = wp_get_split_term( $old_term_id, $taxonomy );
1184 + if ( $new_term_id ) {
1185 + $term_id = $new_term_id;
1186 + }
1187 +
1188 + return $term_id;
1189 + }
1190 +
1191 + /**
1192 + * Upgrade routine to go through all widgets and move the Post Type
1193 + * setting to its newer location.
1194 + *
1195 + * @since 4.7.1
1196 + */
1197 + public static function migrate_post_type_rules() {
1198 + global $wp_widget_factory, $wp_registered_widgets;
1199 + '@phan-var \WP_Widget_Factory $wp_widget_factory';
1200 +
1201 + $sidebars_widgets = get_option( 'sidebars_widgets' );
1202 +
1203 + if ( ! is_array( $sidebars_widgets ) ) {
1204 + return;
1205 + }
1206 +
1207 + // Going through all sidebars and through inactive and orphaned widgets.
1208 + foreach ( $sidebars_widgets as $sidebar ) {
1209 + if ( ! is_array( $sidebar ) ) {
1210 + continue;
1211 + }
1212 +
1213 + foreach ( $sidebar as $widget ) {
1214 + // $widget is the id of the widget
1215 + if ( empty( $wp_registered_widgets[ $widget ] ) ) {
1216 + continue;
1217 + }
1218 +
1219 + $id_base = wp_parse_widget_id( $widget )['id_base'];
1220 + $widget_object = $wp_widget_factory->get_widget_object( $id_base );
1221 +
1222 + if ( ! $widget_object ) {
1223 + continue;
1224 + }
1225 +
1226 + $instances = get_option( $widget_object->option_name );
1227 +
1228 + if ( ! is_array( $instances ) || empty( $instances ) ) {
1229 + continue;
1230 + }
1231 +
1232 + // Going through each instance of the widget.
1233 + foreach ( $instances as $number => $instance ) {
1234 + if (
1235 + ! is_array( $instance ) ||
1236 + empty( $instance['conditions']['rules'] ) ||
1237 + ! is_array( $instance['conditions']['rules'] )
1238 + ) {
1239 + continue;
1240 + }
1241 +
1242 + // Going through all visibility rules.
1243 + foreach ( $instance['conditions']['rules'] as $index => $rule ) {
1244 +
1245 + // We only need Post Type rules.
1246 + if ( 'post_type' !== $rule['major'] ) {
1247 + continue;
1248 + }
1249 +
1250 + $rule_type = false;
1251 +
1252 + // Post type or type archive rule.
1253 + if ( str_starts_with( $rule['minor'], 'post_type_archive' ) ) {
1254 + $rule_type = 'post_type_archive';
1255 + } elseif ( str_starts_with( $rule['minor'], 'post_type' ) ) {
1256 + $rule_type = 'post_type';
1257 + }
1258 +
1259 + if ( $rule_type ) {
1260 + $post_type = substr( $rule['minor'], strlen( $rule_type ) + 1 );
1261 + $rule['minor'] = $rule_type . '-' . $post_type;
1262 + $rule['major'] = 'page';
1263 +
1264 + $instances[ $number ]['conditions']['rules'][ $index ] = $rule;
1265 + }
1266 + }
1267 + }
1268 +
1269 + update_option( $widget_object->option_name, $instances );
1270 + }
1271 + }
1272 + }
1273 +}
1274 +
1275 +add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
1276 +
1277 +// Add the 'conditions' attribute to server side rendered blocks
1278 +// 'init' happens too late to hook on block registration.
1279 +global $pagenow;
1280 +$current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
1281 +if ( is_customize_preview()
1282 + || 'widgets.php' === $pagenow
1283 + || str_contains( $current_url, '/wp-json/wp/v2/block-renderer' )
1284 + || 1 === preg_match( '~^/wp/v2/sites/\d+/block-renderer~', $current_url )
1285 +) {
1286 + Jetpack_Widget_Conditions::add_block_attributes_filter();
1287 +}