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