Completed
Push — fix/infinite-scroll-posts-per-... ( 4facc0...c2e235 )
by
unknown
20:49 queued 12:45
created

generate_condition_key()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Assets;
4
5
/**
6
 * Hide or show widgets conditionally.
7
 */
8
9
class Jetpack_Widget_Conditions {
10
	static $passed_template_redirect = false;
11
12
	public static function init() {
13
		if ( is_admin() ) {
14
			add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
15
			add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
16
			add_action( 'in_widget_form', array( __CLASS__, 'widget_conditions_admin' ), 10, 3 );
17
		} elseif ( ! in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) ) {
18
			add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
19
			add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
20
			add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ) );
21
		}
22
	}
23
24
	public static function widget_admin_setup() {
25
		$current_screen = get_current_screen();
26
		// TODO: Replace `$current_screen->is_block_editor()` with `wp_should_load_block_editor_scripts_and_styles()` that is introduced in WP 5.6
27
		if ( method_exists( $current_screen, 'is_block_editor' ) && $current_screen->is_block_editor() ) {
28
			return;
29
		}
30
31
		wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ) );
32
		wp_style_add_data( 'widget-conditions', 'rtl', 'replace' );
33
		wp_enqueue_script(
34
			'widget-conditions',
35
			Assets::get_file_url_for_environment(
36
				'_inc/build/widget-visibility/widget-conditions/widget-conditions.min.js',
37
				'modules/widget-visibility/widget-conditions/widget-conditions.js'
38
			),
39
			array( 'jquery', 'jquery-ui-core' ),
40
			20191128,
41
			true
42
		);
43
44
		// Set up a single copy of all of the data that Widget Visibility needs.
45
		// This allows all widget conditions to reuse the same data, keeping page size down
46
		// and eliminating the AJAX calls we used to have to use to fetch the minor rule options.
47
		$widget_conditions_data = array();
48
49
		$widget_conditions_data['category']   = array();
50
		$widget_conditions_data['category'][] = array( '', __( 'All category pages', 'jetpack' ) );
51
52
		$categories = get_categories(
53
			array(
54
				'number'  => 1000,
55
				'orderby' => 'count',
56
				'order'   => 'DESC',
57
			)
58
		);
59
		usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
60
61
		foreach ( $categories as $category ) {
62
			$widget_conditions_data['category'][] = array( (string) $category->term_id, $category->name );
63
		}
64
65
		$widget_conditions_data['loggedin']   = array();
66
		$widget_conditions_data['loggedin'][] = array( 'loggedin', __( 'Logged In', 'jetpack' ) );
67
		$widget_conditions_data['loggedin'][] = array( 'loggedout', __( 'Logged Out', 'jetpack' ) );
68
69
		$widget_conditions_data['author']   = array();
70
		$widget_conditions_data['author'][] = array( '', __( 'All author pages', 'jetpack' ) );
71
72
		// Only users with publish caps
73
		$authors = get_users(
74
			array(
75
				'orderby' => 'name',
76
				'who'     => 'authors',
77
				'fields'  => array( 'ID', 'display_name' ),
78
			)
79
		);
80
81
		foreach ( $authors as $author ) {
82
			$widget_conditions_data['author'][] = array( (string) $author->ID, $author->display_name );
83
		}
84
85
		$widget_conditions_data['role'] = array();
86
87
		global $wp_roles;
88
89
		foreach ( $wp_roles->roles as $role_key => $role ) {
90
			$widget_conditions_data['role'][] = array( (string) $role_key, $role['name'] );
91
		}
92
93
		$widget_conditions_data['tag']   = array();
94
		$widget_conditions_data['tag'][] = array( '', __( 'All tag pages', 'jetpack' ) );
95
96
		$tags = get_tags(
97
			array(
98
				'number'  => 1000,
99
				'orderby' => 'count',
100
				'order'   => 'DESC',
101
			)
102
		);
103
		usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
104
105
		foreach ( $tags as $tag ) {
106
			$widget_conditions_data['tag'][] = array( (string) $tag->term_id, $tag->name );
107
		}
108
109
		$widget_conditions_data['date']   = array();
110
		$widget_conditions_data['date'][] = array( '', __( 'All date archives', 'jetpack' ) );
111
		$widget_conditions_data['date'][] = array( 'day', __( 'Daily archives', 'jetpack' ) );
112
		$widget_conditions_data['date'][] = array( 'month', __( 'Monthly archives', 'jetpack' ) );
113
		$widget_conditions_data['date'][] = array( 'year', __( 'Yearly archives', 'jetpack' ) );
114
115
		$widget_conditions_data['page']   = array();
116
		$widget_conditions_data['page'][] = array( 'front', __( 'Front page', 'jetpack' ) );
117
		$widget_conditions_data['page'][] = array( 'posts', __( 'Posts page', 'jetpack' ) );
118
		$widget_conditions_data['page'][] = array( 'archive', __( 'Archive page', 'jetpack' ) );
119
		$widget_conditions_data['page'][] = array( '404', __( '404 error page', 'jetpack' ) );
120
		$widget_conditions_data['page'][] = array( 'search', __( 'Search results', 'jetpack' ) );
121
122
		$post_types = get_post_types( array( 'public' => true ), 'objects' );
123
124
		$widget_conditions_post_types         = array();
125
		$widget_conditions_post_type_archives = array();
126
127
		foreach ( $post_types as $post_type ) {
128
			$widget_conditions_post_types[]         = array( 'post_type-' . $post_type->name, $post_type->labels->singular_name );
129
			$widget_conditions_post_type_archives[] = array( 'post_type_archive-' . $post_type->name, $post_type->labels->name );
130
		}
131
132
		$widget_conditions_data['page'][] = array( __( 'Post type:', 'jetpack' ), $widget_conditions_post_types );
133
134
		$widget_conditions_data['page'][] = array( __( 'Post type Archives:', 'jetpack' ), $widget_conditions_post_type_archives );
135
136
		$pages = self::get_pages();
137
138
		$dropdown_tree_args = array(
139
			'depth'                 => 0,
140
			'child_of'              => 0,
141
			'selected'              => 0,
142
			'echo'                  => false,
143
			'name'                  => 'page_id',
144
			'id'                    => '',
145
			'class'                 => '',
146
			'show_option_none'      => '',
147
			'show_option_no_change' => '',
148
			'option_none_value'     => '',
149
			'value_field'           => 'ID',
150
		);
151
		$pages_dropdown = walk_page_dropdown_tree( $pages, 0, $dropdown_tree_args );
152
		preg_match_all( '/value=.([0-9]+).[^>]*>([^<]+)</', $pages_dropdown, $page_ids_and_titles, PREG_SET_ORDER );
153
		$static_pages = array();
154
155
		foreach ( $page_ids_and_titles as $page_id_and_title ) {
0 ignored issues
show
Bug introduced by
The expression $page_ids_and_titles of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
156
			$static_pages[] = array( (string) $page_id_and_title[1], $page_id_and_title[2] );
157
		}
158
159
		$widget_conditions_data['page'][] = array( __( 'Static page:', 'jetpack' ), $static_pages );
160
161
		$widget_conditions_data['taxonomy']   = array();
162
		$widget_conditions_data['taxonomy'][] = array( '', __( 'All taxonomy pages', 'jetpack' ) );
163
164
		$taxonomies = get_taxonomies(
165
			/**
166
			 * Filters args passed to get_taxonomies.
167
			 *
168
			 * @see https://developer.wordpress.org/reference/functions/get_taxonomies/
169
			 *
170
			 * @since 5.3.0
171
			 *
172
			 * @module widget-visibility
173
			 *
174
			 * @param array $args Widget Visibility taxonomy arguments.
175
			 */
176
			apply_filters( 'jetpack_widget_visibility_tax_args', array( '_builtin' => false ) ),
177
			'objects'
178
		);
179
180
		usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
181
182
		foreach ( $taxonomies as $taxonomy ) {
183
			$taxonomy_terms = get_terms(
184
				array( $taxonomy->name ),
185
				array(
186
					'number'     => 250,
187
					'hide_empty' => false,
188
				)
189
			);
190
191
			$widget_conditions_terms   = array();
192
			$widget_conditions_terms[] = array( $taxonomy->name, __( 'All pages', 'jetpack' ) );
193
194
			foreach ( $taxonomy_terms as $term ) {
195
				$widget_conditions_terms[] = array( $taxonomy->name . '_tax_' . $term->term_id, $term->name );
196
			}
197
198
			$widget_conditions_data['taxonomy'][] = array( $taxonomy->labels->name . ':', $widget_conditions_terms );
199
		}
200
201
		wp_localize_script( 'widget-conditions', 'widget_conditions_data', $widget_conditions_data );
202
203
		// Save a list of the IDs of all pages that have children for dynamically showing the "Include children" checkbox.
204
		$all_pages   = self::get_pages();
205
		$all_parents = array();
206
207
		foreach ( $all_pages as $page ) {
208
			if ( $page->post_parent ) {
209
				$all_parents[ (string) $page->post_parent ] = true;
210
			}
211
		}
212
213
		$front_page_id = get_option( 'page_on_front' );
214
215
		if ( isset( $all_parents[ $front_page_id ] ) ) {
216
			$all_parents['front'] = true;
217
		}
218
219
		wp_localize_script( 'widget-conditions', 'widget_conditions_parent_pages', $all_parents );
220
	}
221
222
	/**
223
 	 * Retrieves a full list of all pages, containing just the IDs, post_parent, and post_title fields.
224
 	 *
225
 	 * Since the WordPress' `get_pages` function does not allow us to fetch only the fields mentioned
226
 	 * above, we need to introduce a custom method using a direct SQL query fetching those.
227
 	 *
228
 	 * By fetching only those 3 fields and not populating the object cache for all the pages, we can 
229
 	 * improve the performance of the query on sites having a lot of pages.
230
 	 *
231
 	 * @see https://core.trac.wordpress.org/ticket/51469
232
 	 *
233
 	 * @return array List of all pages on the site (stdClass objects containing ID, post_title, and post_parent only).
234
 	 */
235
	public static function get_pages() {
236
		global $wpdb;
237
238
		$last_changed = wp_cache_get_last_changed( 'posts' );
239
		$cache_key    = "get_pages:$last_changed";
240
		$pages        = wp_cache_get( $cache_key, 'widget_conditions' );
241
		if ( false === $pages ) {
242
			$pages = $wpdb->get_results( "SELECT {$wpdb->posts}.ID, {$wpdb->posts}.post_parent, {$wpdb->posts}.post_title FROM {$wpdb->posts} WHERE {$wpdb->posts}.post_type = 'page' AND {$wpdb->posts}.post_status = 'publish' ORDER BY {$wpdb->posts}.post_title ASC" );
243
			wp_cache_set( $cache_key, $pages, 'widget_conditions' );
244
		}
245
246
		// Copy-pasted from the get_pages function. For usage in the `widget_conditions_get_pages` filter.
247
		$parsed_args = array(
248
			'child_of'     => 0,
249
			'sort_order'   => 'ASC',
250
			'sort_column'  => 'post_title',
251
			'hierarchical' => 1,
252
			'exclude'      => array(),
253
			'include'      => array(),
254
			'meta_key'     => '',
255
			'meta_value'   => '',
256
			'authors'      => '',
257
			'parent'       => -1,
258
			'exclude_tree' => array(),
259
			'number'       => '',
260
			'offset'       => 0,
261
			'post_type'    => 'page',
262
			'post_status'  => 'publish',
263
		);
264
265
		/**
266
		 * Filters the retrieved list of pages.
267
		 *
268
		 * @since 9.1.0
269
		 *
270
		 * @module widget-visibility
271
		 *
272
		 * @param stdClass[] $pages       Array of objects containing only the ID, post_parent, and post_title fields.
273
		 * @param array      $parsed_args Array of get_pages() arguments.
274
		 */
275
		return apply_filters( 'jetpack_widget_visibility_get_pages', $pages, $parsed_args );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $parsed_args.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
276
	}
277
278
	/**
279
	 * Add the widget conditions to each widget in the admin.
280
	 *
281
	 * @param $widget unused.
282
	 * @param $return unused.
283
	 * @param array         $instance The widget settings.
284
	 */
285
	public static function widget_conditions_admin( $widget, $return, $instance ) {
286
		$conditions = array();
287
288
		if ( isset( $instance['conditions'] ) ) {
289
			$conditions = $instance['conditions'];
290
		}
291
292
		if ( ! isset( $conditions['action'] ) ) {
293
			$conditions['action'] = 'show';
294
		}
295
296
		if ( empty( $conditions['rules'] ) ) {
297
			$conditions['rules'][] = array(
298
				'major'        => '',
299
				'minor'        => '',
300
				'has_children' => '',
301
			);
302
		}
303
304
		if ( empty( $conditions['match_all'] ) ) {
305
			$conditions['match_all'] = false;
306
		}
307
308
		?>
309
		<div
310
			class="
311
				widget-conditional
312
				<?php
313
				if (
314
						empty( $_POST['widget-conditions-visible'] )
315
						|| $_POST['widget-conditions-visible'] == '0'
316
					) {
317
					?>
318
						widget-conditional-hide
319
						<?php
320
				}
321
				?>
322
				<?php
323
				if ( ! empty( $conditions['match_all'] ) && $conditions['match_all'] ) {
324
					?>
325
						intersection
326
						<?php
327
				} else {
328
					?>
329
						conjunction
330
						<?php
331
				}
332
				?>
333
			">
334
			<input type="hidden" name="widget-conditions-visible" value="
335
			<?php
336
			if ( isset( $_POST['widget-conditions-visible'] ) ) {
337
				echo esc_attr( $_POST['widget-conditions-visible'] ); } else {
338
				?>
339
				0<?php } ?>" />
340
			<?php
341
			if ( ! isset( $_POST['widget-conditions-visible'] ) ) {
342
				?>
343
				<a href="#" class="button display-options"><?php _e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
344
			<div class="widget-conditional-inner">
345
				<div class="condition-top">
346
					<?php printf( _x( '%s if:', 'placeholder: dropdown menu to select widget visibility; hide if or show if', 'jetpack' ), '<select name="conditions[action]"><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><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></select>' ); ?>
347
				</div><!-- .condition-top -->
348
349
				<div class="conditions">
350
					<?php
351
352
					foreach ( $conditions['rules'] as $rule_index => $rule ) {
353
						$rule = wp_parse_args(
354
							$rule,
355
							array(
0 ignored issues
show
Documentation introduced by
array('major' => '', 'mi..., 'has_children' => '') is of type array<string,string,{"ma...as_children":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356
								'major'        => '',
357
								'minor'        => '',
358
								'has_children' => '',
359
							)
360
						);
361
						?>
362
						<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'] ); ?>">
363
							<div class="selection alignleft">
364
								<select class="conditions-rule-major" name="conditions[rules_major][]">
365
									<option value="" <?php selected( '', $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
366
									<option value="category" <?php selected( 'category', $rule['major'] ); ?>><?php esc_html_e( 'Category', 'jetpack' ); ?></option>
367
									<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>
368
369
									<?php if ( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { // this doesn't work on .com because of caching ?>
370
										<option value="loggedin" <?php selected( 'loggedin', $rule['major'] ); ?>><?php echo esc_html_x( 'User', 'Noun', 'jetpack' ); ?></option>
371
										<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>
372
									<?php } ?>
373
374
									<option value="tag" <?php selected( 'tag', $rule['major'] ); ?>><?php echo esc_html_x( 'Tag', 'Noun, as in: "This post has one tag."', 'jetpack' ); ?></option>
375
									<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>
376
									<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>
377
									<?php if ( get_taxonomies( array( '_builtin' => false ) ) ) : ?>
378
										<option value="taxonomy" <?php selected( 'taxonomy', $rule['major'] ); ?>><?php echo esc_html_x( 'Taxonomy', 'Noun, as in: "This post has one taxonomy."', 'jetpack' ); ?></option>
379
									<?php endif; ?>
380
								</select>
381
382
								<?php _ex( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>
383
384
								<select class="conditions-rule-minor" name="conditions[rules_minor][]"
385
								<?php
386
								if ( ! $rule['major'] ) {
387
									?>
388
									 disabled="disabled"<?php } ?>>
389
									<?php
390
									/*
391
									Include the currently selected value so that if the widget is saved without
392
											 expanding the Visibility section, we don't lose the minor part of the rule.
393
											 If it is opened, this list is cleared out and populated with all the values. */
394
									?>
395
									<option value="<?php echo esc_attr( $rule['minor'] ); ?>" selected="selected"></option>
396
								</select>
397
398
								<span class="conditions-rule-has-children"
399
								<?php
400
								if ( ! $rule['has_children'] ) {
401
									?>
402
									 style="display: none;"<?php } ?>>
403
									<label>
404
										<input type="checkbox" name="conditions[page_children][<?php echo $rule_index; ?>]" value="has" <?php checked( $rule['has_children'], true ); ?> />
405
										<?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' ); ?>
406
									</label>
407
								</span>
408
							</div>
409
410
							<div class="condition-control">
411
								<span class="condition-conjunction">
412
									<?php echo esc_html_x( 'or', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
413
								</span>
414
								<span class="condition-intersection">
415
									<?php echo esc_html_x( 'and', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
416
								</span>
417
								<div class="actions alignright">
418
									<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>
419
								</div>
420
							</div>
421
422
						</div><!-- .condition -->
423
						<?php
424
					}
425
426
					?>
427
				</div><!-- .conditions -->
428
				<div class="conditions">
429
					<div class="condition-top">
430
						<label>
431
							<input
432
								type="checkbox"
433
								name="conditions[match_all]"
434
								value="1"
435
								class="conditions-match-all"
436
								<?php checked( $conditions['match_all'], '1' ); ?> />
437
							<?php esc_html_e( 'Match all conditions', 'jetpack' ); ?>
438
						</label>
439
					</div><!-- .condition-top -->
440
				</div><!-- .conditions -->
441
			</div><!-- .widget-conditional-inner -->
442
		</div><!-- .widget-conditional -->
443
		<?php
444
	}
445
446
	/**
447
	 * On an AJAX update of the widget settings, process the display conditions.
448
	 *
449
	 * @param array $new_instance New settings for this instance as input by the user.
450
	 * @param array $old_instance Old settings for this instance.
451
	 * @return array Modified settings.
452
	 */
453
	public static function widget_update( $instance, $new_instance, $old_instance ) {
454
		if ( empty( $_POST['conditions'] ) ) {
455
			return $instance;
456
		}
457
458
		$conditions              = array();
459
		$conditions['action']    = $_POST['conditions']['action'];
460
		$conditions['match_all'] = ( isset( $_POST['conditions']['match_all'] ) ? '1' : '0' );
461
		$conditions['rules']     = array();
462
463
		foreach ( $_POST['conditions']['rules_major'] as $index => $major_rule ) {
464
			if ( ! $major_rule ) {
465
				continue;
466
			}
467
468
			$conditions['rules'][] = array(
469
				'major'        => $major_rule,
470
				'minor'        => isset( $_POST['conditions']['rules_minor'][ $index ] ) ? $_POST['conditions']['rules_minor'][ $index ] : '',
471
				'has_children' => isset( $_POST['conditions']['page_children'][ $index ] ) ? true : false,
472
			);
473
		}
474
475
		if ( ! empty( $conditions['rules'] ) ) {
476
			$instance['conditions'] = $conditions;
477
		} else {
478
			unset( $instance['conditions'] );
479
		}
480
481
		if (
482
				( isset( $instance['conditions'] ) && ! isset( $old_instance['conditions'] ) )
483
				||
484
				(
485
					isset( $instance['conditions'], $old_instance['conditions'] )
486
					&&
487
					serialize( $instance['conditions'] ) != serialize( $old_instance['conditions'] )
488
				)
489
			) {
490
491
			/**
492
			 * Fires after the widget visibility conditions are saved.
493
			 *
494
			 * @module widget-visibility
495
			 *
496
			 * @since 2.4.0
497
			 */
498
			do_action( 'widget_conditions_save' );
499
		} elseif ( ! isset( $instance['conditions'] ) && isset( $old_instance['conditions'] ) ) {
500
501
			/**
502
			 * Fires after the widget visibility conditions are deleted.
503
			 *
504
			 * @module widget-visibility
505
			 *
506
			 * @since 2.4.0
507
			 */
508
			do_action( 'widget_conditions_delete' );
509
		}
510
511
		return $instance;
512
	}
513
514
	/**
515
	 * Filter the list of widgets for a sidebar so that active sidebars work as expected.
516
	 *
517
	 * @param array $widget_areas An array of widget areas and their widgets.
518
	 * @return array The modified $widget_area array.
519
	 */
520
	public static function sidebars_widgets( $widget_areas ) {
521
		$settings = array();
522
523
		foreach ( $widget_areas as $widget_area => $widgets ) {
524
			if ( empty( $widgets ) ) {
525
				continue;
526
			}
527
528
			if ( ! is_array( $widgets ) ) {
529
				continue;
530
			}
531
532
			if ( 'wp_inactive_widgets' == $widget_area ) {
533
				continue;
534
			}
535
536
			foreach ( $widgets as $position => $widget_id ) {
537
				// Find the conditions for this widget.
538
				if ( preg_match( '/^(.+?)-(\d+)$/', $widget_id, $matches ) ) {
539
					$id_base       = $matches[1];
540
					$widget_number = (int) $matches[2];
541
				} else {
542
					$id_base       = $widget_id;
543
					$widget_number = null;
544
				}
545
546
				if ( ! isset( $settings[ $id_base ] ) ) {
547
					$settings[ $id_base ] = get_option( 'widget_' . $id_base );
548
				}
549
550
				// New multi widget (WP_Widget)
551
				if ( ! is_null( $widget_number ) ) {
552
					if ( isset( $settings[ $id_base ][ $widget_number ] ) && false === self::filter_widget( $settings[ $id_base ][ $widget_number ] ) ) {
553
						unset( $widget_areas[ $widget_area ][ $position ] );
554
					}
555
				}
556
557
				// Old single widget
558
				elseif ( ! empty( $settings[ $id_base ] ) && false === self::filter_widget( $settings[ $id_base ] ) ) {
559
					unset( $widget_areas[ $widget_area ][ $position ] );
560
				}
561
			}
562
		}
563
564
		return $widget_areas;
565
	}
566
567
	public static function template_redirect() {
568
		self::$passed_template_redirect = true;
569
	}
570
571
	/**
572
	 * Generates a condition key based on the rule array
573
	 *
574
	 * @param array $rule
575
	 * @return string key used to retrieve the condition.
576
	 */
577
	static function generate_condition_key( $rule ) {
578
		if ( isset( $rule['has_children'] ) ) {
579
			return $rule['major'] . ':' . $rule['minor'] . ':' . $rule['has_children'];
580
		}
581
		return $rule['major'] . ':' . $rule['minor'];
582
	}
583
584
	/**
585
	 * Determine whether the widget should be displayed based on conditions set by the user.
586
	 *
587
	 * @param array $instance The widget settings.
588
	 * @return array Settings to display or bool false to hide.
589
	 */
590
	public static function filter_widget( $instance ) {
591
		global $wp_query;
592
593
		if ( empty( $instance['conditions'] ) || empty( $instance['conditions']['rules'] ) ) {
594
			return $instance;
595
		}
596
597
		// Store the results of all in-page condition lookups so that multiple widgets with
598
		// the same visibility conditions don't result in duplicate DB queries.
599
		static $condition_result_cache = array();
600
601
		$condition_result = false;
602
603
		foreach ( $instance['conditions']['rules'] as $rule ) {
604
			$condition_result = false;
605
			$condition_key    = self::generate_condition_key( $rule );
606
607
			if ( isset( $condition_result_cache[ $condition_key ] ) ) {
608
				$condition_result = $condition_result_cache[ $condition_key ];
609
			} else {
610
				switch ( $rule['major'] ) {
611
					case 'date':
612
						switch ( $rule['minor'] ) {
613
							case '':
614
								$condition_result = is_date();
615
								break;
616
							case 'month':
617
								$condition_result = is_month();
618
								break;
619
							case 'day':
620
								$condition_result = is_day();
621
								break;
622
							case 'year':
623
								$condition_result = is_year();
624
								break;
625
						}
626
						break;
627
					case 'page':
628
						// Previously hardcoded post type options.
629
						if ( 'post' == $rule['minor'] ) {
630
							$rule['minor'] = 'post_type-post';
631
						} elseif ( ! $rule['minor'] ) {
632
							$rule['minor'] = 'post_type-page';
633
						}
634
635
						switch ( $rule['minor'] ) {
636
							case '404':
637
								$condition_result = is_404();
638
								break;
639
							case 'search':
640
								$condition_result = is_search();
641
								break;
642
							case 'archive':
643
								$condition_result = is_archive();
644
								break;
645
							case 'posts':
646
								$condition_result = $wp_query->is_posts_page;
647
								break;
648
							case 'home':
649
								$condition_result = is_home();
650
								break;
651
							case 'front':
652
								if ( current_theme_supports( 'infinite-scroll' ) ) {
653
									$condition_result = is_front_page();
654
								} else {
655
									$condition_result = is_front_page() && ! is_paged();
656
								}
657
								break;
658
							default:
659
								if ( substr( $rule['minor'], 0, 10 ) == 'post_type-' ) {
660
									$condition_result = is_singular( substr( $rule['minor'], 10 ) );
661 View Code Duplication
								} elseif ( substr( $rule['minor'], 0, 18 ) == 'post_type_archive-' ) {
662
									$condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
663
								} elseif ( $rule['minor'] == get_option( 'page_for_posts' ) ) {
664
									// If $rule['minor'] is a page ID which is also the posts page
665
									$condition_result = $wp_query->is_posts_page;
666
								} else {
667
									// $rule['minor'] is a page ID
668
									$condition_result = is_page() && ( $rule['minor'] == get_the_ID() );
669
670
									// Check if $rule['minor'] is parent of page ID
671
									if ( ! $condition_result && isset( $rule['has_children'] ) && $rule['has_children'] ) {
672
										$condition_result = wp_get_post_parent_id( get_the_ID() ) == $rule['minor'];
673
									}
674
								}
675
								break;
676
						}
677
						break;
678 View Code Duplication
					case 'tag':
679
						// All tag pages.
680
						if ( ! $rule['minor'] ) {
681
							if ( is_tag() ) {
682
								$condition_result = true;
683
							} elseif ( is_singular() ) {
684
								if ( in_array( 'post_tag', get_post_taxonomies() ) ) {
685
									$condition_result = true;
686
								}
687
							}
688
							break;
689
						}
690
691
						// All pages with the specified tag term.
692
						if ( is_tag( $rule['minor'] ) ) {
693
							$condition_result = true;
694
						} elseif ( is_singular() && has_term( $rule['minor'], 'post_tag' ) ) {
695
							$condition_result = true;
696
						}
697
						break;
698 View Code Duplication
					case 'category':
699
						// All category pages.
700
						if ( ! $rule['minor'] ) {
701
							if ( is_category() ) {
702
								$condition_result = true;
703
							} elseif ( is_singular() ) {
704
								if ( in_array( 'category', get_post_taxonomies() ) ) {
705
									$condition_result = true;
706
								}
707
							}
708
							break;
709
						}
710
711
						// All pages with the specified category term.
712
						if ( is_category( $rule['minor'] ) ) {
713
							$condition_result = true;
714
						} elseif ( is_singular() && has_term( $rule['minor'], 'category' ) ) {
715
							$condition_result = true;
716
						}
717
						break;
718
					case 'loggedin':
719
						$condition_result = is_user_logged_in();
720
						if ( 'loggedin' !== $rule['minor'] ) {
721
							$condition_result = ! $condition_result;
722
						}
723
						break;
724
					case 'author':
725
						$post = get_post();
726
						if ( ! $rule['minor'] && is_author() ) {
727
							$condition_result = true;
728
						} elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
729
							$condition_result = true;
730
						} elseif ( is_singular() && $rule['minor'] && $rule['minor'] == $post->post_author ) {
731
							$condition_result = true;
732
						}
733
						break;
734
					case 'role':
735
						if ( is_user_logged_in() ) {
736
							$current_user = wp_get_current_user();
737
738
							$user_roles = $current_user->roles;
739
740
							if ( in_array( $rule['minor'], $user_roles ) ) {
741
								$condition_result = true;
742
							} else {
743
								$condition_result = false;
744
							}
745
						} else {
746
							$condition_result = false;
747
						}
748
						break;
749
					case 'post_type':
750
						if ( substr( $rule['minor'], 0, 10 ) == 'post_type-' ) {
751
							$condition_result = is_singular( substr( $rule['minor'], 10 ) );
752 View Code Duplication
						} elseif ( substr( $rule['minor'], 0, 18 ) == 'post_type_archive-' ) {
753
							$condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
754
						}
755
						break;
756
					case 'taxonomy':
757
						// All taxonomy pages.
758
						if ( ! $rule['minor'] ) {
759
							if ( is_archive() ) {
760
								if ( is_tag() || is_category() || is_tax() ) {
761
									$condition_result = true;
762
								}
763
							} elseif ( is_singular() ) {
764
								$post_taxonomies  = get_post_taxonomies();
765
								$condition_result = ! empty( $post_taxonomies );
766
							}
767
							break;
768
						}
769
770
						// Specified taxonomy page.
771
						$term = explode( '_tax_', $rule['minor'] ); // $term[0] = taxonomy name; $term[1] = term id
772
						if ( isset( $term[0] ) && isset( $term[1] ) ) {
773
							$term[1] = self::maybe_get_split_term( $term[1], $term[0] );
774
						}
775
776
						// All pages of the specified taxonomy.
777
						if ( ! isset( $term[1] ) || ! $term[1] ) {
778
							if ( is_tax( $term[0] ) ) {
779
								$condition_result = true;
780
							} elseif ( is_singular() ) {
781
								if ( in_array( $term[0], get_post_taxonomies() ) ) {
782
									$condition_result = true;
783
								}
784
							}
785
							break;
786
						}
787
788
						// All pages with the specified taxonomy term.
789
						if ( is_tax( $term[0], $term[1] ) ) {
790
							$condition_result = true;
791
						} elseif ( is_singular() && has_term( $term[1], $term[0] ) ) {
792
							$condition_result = true;
793
						}
794
						break;
795
				}
796
797
				if ( $condition_result || self::$passed_template_redirect ) {
798
					// Some of the conditions will return false when checked before the template_redirect
799
					// action has been called, like is_page(). Only store positive lookup results, which
800
					// won't be false positives, before template_redirect, and everything after.
801
					$condition_result_cache[ $condition_key ] = $condition_result;
802
				}
803
			}
804
805
			if (
806
				isset( $instance['conditions']['match_all'] )
807
				&& $instance['conditions']['match_all'] == '1'
808
				&& ! $condition_result
809
			) {
810
811
				// In case the match_all flag was set we quit on first failed condition
812
				break;
813
			} elseif (
814
				(
815
					empty( $instance['conditions']['match_all'] )
816
					|| $instance['conditions']['match_all'] !== '1'
817
				)
818
				&& $condition_result
819
			) {
820
821
				// Only quit on first condition if the match_all flag was not set
822
				break;
823
			}
824
		}
825
826
		if (
827
			(
828
				'show' == $instance['conditions']['action']
829
				&& ! $condition_result
830
			) || (
831
				'hide' == $instance['conditions']['action']
832
				&& $condition_result
833
			)
834
		) {
835
			return false;
836
		}
837
838
		return $instance;
839
	}
840
841
	public static function strcasecmp_name( $a, $b ) {
842
		return strcasecmp( $a->name, $b->name );
843
	}
844
845
	public static function maybe_get_split_term( $old_term_id = '', $taxonomy = '' ) {
846
		$term_id = $old_term_id;
847
848
		if ( 'tag' == $taxonomy ) {
849
			$taxonomy = 'post_tag';
850
		}
851
852
		if ( $new_term_id = wp_get_split_term( $old_term_id, $taxonomy ) ) {
853
			$term_id = $new_term_id;
854
		}
855
856
		return $term_id;
857
	}
858
859
	/**
860
	 * Upgrade routine to go through all widgets and move the Post Type
861
	 * setting to its newer location.
862
	 *
863
	 * @since 4.7.1
864
	 */
865
	static function migrate_post_type_rules() {
866
		global $wp_registered_widgets;
867
868
		$sidebars_widgets = get_option( 'sidebars_widgets' );
869
870
		// Going through all sidebars and through inactive and orphaned widgets
871
		foreach ( $sidebars_widgets as $s => $sidebar ) {
872
			if ( ! is_array( $sidebar ) ) {
873
				continue;
874
			}
875
876
			foreach ( $sidebar as $w => $widget ) {
877
				// $widget is the id of the widget
878
				if ( empty( $wp_registered_widgets[ $widget ] ) ) {
879
					continue;
880
				}
881
882
				$opts      = $wp_registered_widgets[ $widget ];
883
				$instances = get_option( $opts['callback'][0]->option_name );
884
885
				// Going through each instance of the widget
886
				foreach ( $instances as $number => $instance ) {
887
					if (
888
						! is_array( $instance ) ||
889
						empty( $instance['conditions'] ) ||
890
						empty( $instance['conditions']['rules'] )
891
					) {
892
						continue;
893
					}
894
895
					// Going through all visibility rules
896
					foreach ( $instance['conditions']['rules'] as $index => $rule ) {
897
898
						// We only need Post Type rules
899
						if ( 'post_type' !== $rule['major'] ) {
900
							continue;
901
						}
902
903
						$rule_type = false;
904
905
						// Post type or type archive rule
906
						if ( 0 === strpos( $rule['minor'], 'post_type_archive' ) ) {
907
							$rule_type = 'post_type_archive';
908
						} elseif ( 0 === strpos( $rule['minor'], 'post_type' ) ) {
909
							$rule_type = 'post_type';
910
						}
911
912
						if ( $rule_type ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rule_type of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
913
							$post_type     = substr( $rule['minor'], strlen( $rule_type ) + 1 );
914
							$rule['minor'] = $rule_type . '-' . $post_type;
915
							$rule['major'] = 'page';
916
917
							$instances[ $number ]['conditions']['rules'][ $index ] = $rule;
918
						}
919
					}
920
				}
921
922
				update_option( $opts['callback'][0]->option_name, $instances );
923
			}
924
		}
925
	}
926
927
}
928
929
add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
930