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