Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
9.95% covered (danger)
9.95%
63 / 633
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Widget_Conditions
10.10% covered (danger)
10.10%
63 / 624
0.00% covered (danger)
0.00%
0 / 16
43151.90
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
1056
 setup_block_controls
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 add_block_attributes_filter
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 widget_admin_setup
0.00% covered (danger)
0.00%
0 / 122
0.00% covered (danger)
0.00%
0 / 1
156
 get_pages
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 widget_conditions_admin
0.00% covered (danger)
0.00%
0 / 112
0.00% covered (danger)
0.00%
0 / 1
306
 widget_update
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
306
 sidebars_widgets
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
210
 template_redirect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generate_condition_key
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 normalize_widget_content
72.73% covered (warning)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
10.64
 filter_widget
73.33% covered (warning)
73.33%
22 / 30
0.00% covered (danger)
0.00%
0 / 1
31.18
 filter_widget_check_conditions
20.00% covered (danger)
20.00%
31 / 155
0.00% covered (danger)
0.00%
0 / 1
4144.55
 strcasecmp_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 maybe_get_split_term
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 migrate_post_type_rules
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
342
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
8use Automattic\Block_Scanner;
9use Automattic\Jetpack\Assets;
10
11if ( ! 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 */
31class 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']    = isset( $new_instance['conditions']['action'] ) ? $new_instance['conditions']['action'] : null;
672        $conditions['match_all'] = ! empty( $new_instance['conditions']['match_all'] ) ? '1' : '0';
673        $conditions['rules']     = isset( $new_instance['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'        => isset( $new_instance['conditions']['rules_minor'][ $index ] ) ? $new_instance['conditions']['rules_minor'][ $index ] : '',
684                    'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ) ? true : false,
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( isset( $instance['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
1275add_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.
1279global $pagenow;
1280$current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
1281if ( 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}