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