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