Completed
Push — update/pre_connection_jitms_pl... ( 9bf136...a14e59 )
by
unknown
214:58 queued 207:06
created

widget_conditions_admin()   F

Complexity

Conditions 17
Paths 4352

Size

Total Lines 160

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
nc 4352
nop 3
dl 0
loc 160
rs 0.8399
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
				/**
55
				 * Specific a maximum number of categories to query for the Widget visibility UI.
56
				 *
57
				 * @module widget-visibility
58
				 *
59
				 * @since 9.1.0
60
				 *
61
				 * @param int $number Maximum number of categories displayed in the Widget visibility UI.
62
				 */
63
				'number'  => (int) apply_filters( 'jetpack_widget_visibility_max_number_categories', 1000 ),
64
				'orderby' => 'count',
65
				'order'   => 'DESC',
66
			)
67
		);
68
		usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
69
70
		foreach ( $categories as $category ) {
71
			$widget_conditions_data['category'][] = array( (string) $category->term_id, $category->name );
72
		}
73
74
		$widget_conditions_data['loggedin']   = array();
75
		$widget_conditions_data['loggedin'][] = array( 'loggedin', __( 'Logged In', 'jetpack' ) );
76
		$widget_conditions_data['loggedin'][] = array( 'loggedout', __( 'Logged Out', 'jetpack' ) );
77
78
		$widget_conditions_data['author']   = array();
79
		$widget_conditions_data['author'][] = array( '', __( 'All author pages', 'jetpack' ) );
80
81
		// Only users with publish caps
82
		$authors = get_users(
83
			array(
84
				'orderby' => 'name',
85
				'who'     => 'authors',
86
				'fields'  => array( 'ID', 'display_name' ),
87
			)
88
		);
89
90
		foreach ( $authors as $author ) {
91
			$widget_conditions_data['author'][] = array( (string) $author->ID, $author->display_name );
92
		}
93
94
		$widget_conditions_data['role'] = array();
95
96
		global $wp_roles;
97
98
		foreach ( $wp_roles->roles as $role_key => $role ) {
99
			$widget_conditions_data['role'][] = array( (string) $role_key, $role['name'] );
100
		}
101
102
		$widget_conditions_data['tag']   = array();
103
		$widget_conditions_data['tag'][] = array( '', __( 'All tag pages', 'jetpack' ) );
104
105
		$tags = get_tags(
106
			array(
107
				/**
108
				 * Specific a maximum number of tags to query for the Widget visibility UI.
109
				 *
110
				 * @module widget-visibility
111
				 *
112
				 * @since 9.1.0
113
				 *
114
				 * @param int $number Maximum number of tags displayed in the Widget visibility UI.
115
				 */
116
				'number'  => (int) apply_filters( 'jetpack_widget_visibility_max_number_tags', 1000 ),
117
				'orderby' => 'count',
118
				'order'   => 'DESC',
119
			)
120
		);
121
		usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
122
123
		foreach ( $tags as $tag ) {
124
			$widget_conditions_data['tag'][] = array( (string) $tag->term_id, $tag->name );
125
		}
126
127
		$widget_conditions_data['date']   = array();
128
		$widget_conditions_data['date'][] = array( '', __( 'All date archives', 'jetpack' ) );
129
		$widget_conditions_data['date'][] = array( 'day', __( 'Daily archives', 'jetpack' ) );
130
		$widget_conditions_data['date'][] = array( 'month', __( 'Monthly archives', 'jetpack' ) );
131
		$widget_conditions_data['date'][] = array( 'year', __( 'Yearly archives', 'jetpack' ) );
132
133
		$widget_conditions_data['page']   = array();
134
		$widget_conditions_data['page'][] = array( 'front', __( 'Front page', 'jetpack' ) );
135
		$widget_conditions_data['page'][] = array( 'posts', __( 'Posts page', 'jetpack' ) );
136
		$widget_conditions_data['page'][] = array( 'archive', __( 'Archive page', 'jetpack' ) );
137
		$widget_conditions_data['page'][] = array( '404', __( '404 error page', 'jetpack' ) );
138
		$widget_conditions_data['page'][] = array( 'search', __( 'Search results', 'jetpack' ) );
139
140
		$post_types = get_post_types( array( 'public' => true ), 'objects' );
141
142
		$widget_conditions_post_types         = array();
143
		$widget_conditions_post_type_archives = array();
144
145
		foreach ( $post_types as $post_type ) {
146
			$widget_conditions_post_types[]         = array( 'post_type-' . $post_type->name, $post_type->labels->singular_name );
147
			$widget_conditions_post_type_archives[] = array( 'post_type_archive-' . $post_type->name, $post_type->labels->name );
148
		}
149
150
		$widget_conditions_data['page'][] = array( __( 'Post type:', 'jetpack' ), $widget_conditions_post_types );
151
152
		$widget_conditions_data['page'][] = array( __( 'Post type Archives:', 'jetpack' ), $widget_conditions_post_type_archives );
153
154
		$pages = self::get_pages();
155
156
		$dropdown_tree_args = array(
157
			'depth'                 => 0,
158
			'child_of'              => 0,
159
			'selected'              => 0,
160
			'echo'                  => false,
161
			'name'                  => 'page_id',
162
			'id'                    => '',
163
			'class'                 => '',
164
			'show_option_none'      => '',
165
			'show_option_no_change' => '',
166
			'option_none_value'     => '',
167
			'value_field'           => 'ID',
168
		);
169
		$pages_dropdown = walk_page_dropdown_tree( $pages, 0, $dropdown_tree_args );
170
		preg_match_all( '/value=.([0-9]+).[^>]*>([^<]+)</', $pages_dropdown, $page_ids_and_titles, PREG_SET_ORDER );
171
		$static_pages = array();
172
173
		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...
174
			$static_pages[] = array( (string) $page_id_and_title[1], $page_id_and_title[2] );
175
		}
176
177
		$widget_conditions_data['page'][] = array( __( 'Static page:', 'jetpack' ), $static_pages );
178
179
		$widget_conditions_data['taxonomy']   = array();
180
		$widget_conditions_data['taxonomy'][] = array( '', __( 'All taxonomy pages', 'jetpack' ) );
181
182
		$taxonomies = get_taxonomies(
183
			/**
184
			 * Filters args passed to get_taxonomies.
185
			 *
186
			 * @see https://developer.wordpress.org/reference/functions/get_taxonomies/
187
			 *
188
			 * @since 5.3.0
189
			 *
190
			 * @module widget-visibility
191
			 *
192
			 * @param array $args Widget Visibility taxonomy arguments.
193
			 */
194
			apply_filters( 'jetpack_widget_visibility_tax_args', array( '_builtin' => false ) ),
195
			'objects'
196
		);
197
198
		usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
199
200
		foreach ( $taxonomies as $taxonomy ) {
201
			$taxonomy_terms = get_terms(
202
				array( $taxonomy->name ),
203
				array(
204
					'number'     => 250,
205
					'hide_empty' => false,
206
				)
207
			);
208
209
			$widget_conditions_terms   = array();
210
			$widget_conditions_terms[] = array( $taxonomy->name, __( 'All pages', 'jetpack' ) );
211
212
			foreach ( $taxonomy_terms as $term ) {
213
				$widget_conditions_terms[] = array( $taxonomy->name . '_tax_' . $term->term_id, $term->name );
214
			}
215
216
			$widget_conditions_data['taxonomy'][] = array( $taxonomy->labels->name . ':', $widget_conditions_terms );
217
		}
218
219
		wp_localize_script( 'widget-conditions', 'widget_conditions_data', $widget_conditions_data );
220
221
		// Save a list of the IDs of all pages that have children for dynamically showing the "Include children" checkbox.
222
		$all_pages   = self::get_pages();
223
		$all_parents = array();
224
225
		foreach ( $all_pages as $page ) {
226
			if ( $page->post_parent ) {
227
				$all_parents[ (string) $page->post_parent ] = true;
228
			}
229
		}
230
231
		$front_page_id = get_option( 'page_on_front' );
232
233
		if ( isset( $all_parents[ $front_page_id ] ) ) {
234
			$all_parents['front'] = true;
235
		}
236
237
		wp_localize_script( 'widget-conditions', 'widget_conditions_parent_pages', $all_parents );
238
	}
239
240
	/**
241
 	 * Retrieves a full list of all pages, containing just the IDs, post_parent, and post_title fields.
242
 	 *
243
 	 * Since the WordPress' `get_pages` function does not allow us to fetch only the fields mentioned
244
 	 * above, we need to introduce a custom method using a direct SQL query fetching those.
245
 	 *
246
 	 * By fetching only those 3 fields and not populating the object cache for all the pages, we can 
247
 	 * improve the performance of the query on sites having a lot of pages.
248
 	 *
249
 	 * @see https://core.trac.wordpress.org/ticket/51469
250
 	 *
251
 	 * @return array List of all pages on the site (stdClass objects containing ID, post_title, and post_parent only).
252
 	 */
253
	public static function get_pages() {
254
		global $wpdb;
255
256
		$last_changed = wp_cache_get_last_changed( 'posts' );
257
		$cache_key    = "get_pages:$last_changed";
258
		$pages        = wp_cache_get( $cache_key, 'widget_conditions' );
259
		if ( false === $pages ) {
260
			$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" );
261
			wp_cache_set( $cache_key, $pages, 'widget_conditions' );
262
		}
263
264
		// Copy-pasted from the get_pages function. For usage in the `widget_conditions_get_pages` filter.
265
		$parsed_args = array(
266
			'child_of'     => 0,
267
			'sort_order'   => 'ASC',
268
			'sort_column'  => 'post_title',
269
			'hierarchical' => 1,
270
			'exclude'      => array(),
271
			'include'      => array(),
272
			'meta_key'     => '',
273
			'meta_value'   => '',
274
			'authors'      => '',
275
			'parent'       => -1,
276
			'exclude_tree' => array(),
277
			'number'       => '',
278
			'offset'       => 0,
279
			'post_type'    => 'page',
280
			'post_status'  => 'publish',
281
		);
282
283
		/**
284
		 * Filters the retrieved list of pages.
285
		 *
286
		 * @since 9.1.0
287
		 *
288
		 * @module widget-visibility
289
		 *
290
		 * @param stdClass[] $pages       Array of objects containing only the ID, post_parent, and post_title fields.
291
		 * @param array      $parsed_args Array of get_pages() arguments.
292
		 */
293
		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...
294
	}
295
296
	/**
297
	 * Add the widget conditions to each widget in the admin.
298
	 *
299
	 * @param $widget unused.
300
	 * @param $return unused.
301
	 * @param array         $instance The widget settings.
302
	 */
303
	public static function widget_conditions_admin( $widget, $return, $instance ) {
304
		$conditions = array();
305
306
		if ( isset( $instance['conditions'] ) ) {
307
			$conditions = $instance['conditions'];
308
		}
309
310
		if ( ! isset( $conditions['action'] ) ) {
311
			$conditions['action'] = 'show';
312
		}
313
314
		if ( empty( $conditions['rules'] ) ) {
315
			$conditions['rules'][] = array(
316
				'major'        => '',
317
				'minor'        => '',
318
				'has_children' => '',
319
			);
320
		}
321
322
		if ( empty( $conditions['match_all'] ) ) {
323
			$conditions['match_all'] = false;
324
		}
325
326
		?>
327
		<div
328
			class="
329
				widget-conditional
330
				<?php
331
				if (
332
						empty( $_POST['widget-conditions-visible'] )
333
						|| $_POST['widget-conditions-visible'] == '0'
334
					) {
335
					?>
336
						widget-conditional-hide
337
						<?php
338
				}
339
				?>
340
				<?php
341
				if ( ! empty( $conditions['match_all'] ) && $conditions['match_all'] ) {
342
					?>
343
						intersection
344
						<?php
345
				} else {
346
					?>
347
						conjunction
348
						<?php
349
				}
350
				?>
351
			">
352
			<input type="hidden" name="widget-conditions-visible" value="
353
			<?php
354
			if ( isset( $_POST['widget-conditions-visible'] ) ) {
355
				echo esc_attr( $_POST['widget-conditions-visible'] ); } else {
356
				?>
357
				0<?php } ?>" />
358
			<?php
359
			if ( ! isset( $_POST['widget-conditions-visible'] ) ) {
360
				?>
361
				<a href="#" class="button display-options"><?php _e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
362
			<div class="widget-conditional-inner">
363
				<div class="condition-top">
364
					<?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>' ); ?>
365
				</div><!-- .condition-top -->
366
367
				<div class="conditions">
368
					<?php
369
370
					foreach ( $conditions['rules'] as $rule_index => $rule ) {
371
						$rule = wp_parse_args(
372
							$rule,
373
							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...
374
								'major'        => '',
375
								'minor'        => '',
376
								'has_children' => '',
377
							)
378
						);
379
						?>
380
						<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'] ); ?>">
381
							<div class="selection alignleft">
382
								<select class="conditions-rule-major" name="conditions[rules_major][]">
383
									<option value="" <?php selected( '', $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
384
									<option value="category" <?php selected( 'category', $rule['major'] ); ?>><?php esc_html_e( 'Category', 'jetpack' ); ?></option>
385
									<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>
386
387
									<?php if ( ! ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ) { // this doesn't work on .com because of caching ?>
388
										<option value="loggedin" <?php selected( 'loggedin', $rule['major'] ); ?>><?php echo esc_html_x( 'User', 'Noun', 'jetpack' ); ?></option>
389
										<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>
390
									<?php } ?>
391
392
									<option value="tag" <?php selected( 'tag', $rule['major'] ); ?>><?php echo esc_html_x( 'Tag', 'Noun, as in: "This post has one tag."', 'jetpack' ); ?></option>
393
									<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>
394
									<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>
395
									<?php if ( get_taxonomies( array( '_builtin' => false ) ) ) : ?>
396
										<option value="taxonomy" <?php selected( 'taxonomy', $rule['major'] ); ?>><?php echo esc_html_x( 'Taxonomy', 'Noun, as in: "This post has one taxonomy."', 'jetpack' ); ?></option>
397
									<?php endif; ?>
398
								</select>
399
400
								<?php _ex( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>
401
402
								<select class="conditions-rule-minor" name="conditions[rules_minor][]"
403
								<?php
404
								if ( ! $rule['major'] ) {
405
									?>
406
									 disabled="disabled"<?php } ?>>
407
									<?php
408
									/*
409
									Include the currently selected value so that if the widget is saved without
410
											 expanding the Visibility section, we don't lose the minor part of the rule.
411
											 If it is opened, this list is cleared out and populated with all the values. */
412
									?>
413
									<option value="<?php echo esc_attr( $rule['minor'] ); ?>" selected="selected"></option>
414
								</select>
415
416
								<span class="conditions-rule-has-children"
417
								<?php
418
								if ( ! $rule['has_children'] ) {
419
									?>
420
									 style="display: none;"<?php } ?>>
421
									<label>
422
										<input type="checkbox" name="conditions[page_children][<?php echo $rule_index; ?>]" value="has" <?php checked( $rule['has_children'], true ); ?> />
423
										<?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' ); ?>
424
									</label>
425
								</span>
426
							</div>
427
428
							<div class="condition-control">
429
								<span class="condition-conjunction">
430
									<?php echo esc_html_x( 'or', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
431
								</span>
432
								<span class="condition-intersection">
433
									<?php echo esc_html_x( 'and', 'Shown between widget visibility conditions.', 'jetpack' ); ?>
434
								</span>
435
								<div class="actions alignright">
436
									<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>
437
								</div>
438
							</div>
439
440
						</div><!-- .condition -->
441
						<?php
442
					}
443
444
					?>
445
				</div><!-- .conditions -->
446
				<div class="conditions">
447
					<div class="condition-top">
448
						<label>
449
							<input
450
								type="checkbox"
451
								name="conditions[match_all]"
452
								value="1"
453
								class="conditions-match-all"
454
								<?php checked( $conditions['match_all'], '1' ); ?> />
455
							<?php esc_html_e( 'Match all conditions', 'jetpack' ); ?>
456
						</label>
457
					</div><!-- .condition-top -->
458
				</div><!-- .conditions -->
459
			</div><!-- .widget-conditional-inner -->
460
		</div><!-- .widget-conditional -->
461
		<?php
462
	}
463
464
	/**
465
	 * On an AJAX update of the widget settings, process the display conditions.
466
	 *
467
	 * @param array $new_instance New settings for this instance as input by the user.
468
	 * @param array $old_instance Old settings for this instance.
469
	 * @return array Modified settings.
470
	 */
471
	public static function widget_update( $instance, $new_instance, $old_instance ) {
472
		if ( empty( $_POST['conditions'] ) ) {
473
			return $instance;
474
		}
475
476
		$conditions              = array();
477
		$conditions['action']    = $_POST['conditions']['action'];
478
		$conditions['match_all'] = ( isset( $_POST['conditions']['match_all'] ) ? '1' : '0' );
479
		$conditions['rules']     = array();
480
481
		foreach ( $_POST['conditions']['rules_major'] as $index => $major_rule ) {
482
			if ( ! $major_rule ) {
483
				continue;
484
			}
485
486
			$conditions['rules'][] = array(
487
				'major'        => $major_rule,
488
				'minor'        => isset( $_POST['conditions']['rules_minor'][ $index ] ) ? $_POST['conditions']['rules_minor'][ $index ] : '',
489
				'has_children' => isset( $_POST['conditions']['page_children'][ $index ] ) ? true : false,
490
			);
491
		}
492
493
		if ( ! empty( $conditions['rules'] ) ) {
494
			$instance['conditions'] = $conditions;
495
		} else {
496
			unset( $instance['conditions'] );
497
		}
498
499
		if (
500
				( isset( $instance['conditions'] ) && ! isset( $old_instance['conditions'] ) )
501
				||
502
				(
503
					isset( $instance['conditions'], $old_instance['conditions'] )
504
					&&
505
					serialize( $instance['conditions'] ) != serialize( $old_instance['conditions'] )
506
				)
507
			) {
508
509
			/**
510
			 * Fires after the widget visibility conditions are saved.
511
			 *
512
			 * @module widget-visibility
513
			 *
514
			 * @since 2.4.0
515
			 */
516
			do_action( 'widget_conditions_save' );
517
		} elseif ( ! isset( $instance['conditions'] ) && isset( $old_instance['conditions'] ) ) {
518
519
			/**
520
			 * Fires after the widget visibility conditions are deleted.
521
			 *
522
			 * @module widget-visibility
523
			 *
524
			 * @since 2.4.0
525
			 */
526
			do_action( 'widget_conditions_delete' );
527
		}
528
529
		return $instance;
530
	}
531
532
	/**
533
	 * Filter the list of widgets for a sidebar so that active sidebars work as expected.
534
	 *
535
	 * @param array $widget_areas An array of widget areas and their widgets.
536
	 * @return array The modified $widget_area array.
537
	 */
538
	public static function sidebars_widgets( $widget_areas ) {
539
		$settings = array();
540
541
		foreach ( $widget_areas as $widget_area => $widgets ) {
542
			if ( empty( $widgets ) ) {
543
				continue;
544
			}
545
546
			if ( ! is_array( $widgets ) ) {
547
				continue;
548
			}
549
550
			if ( 'wp_inactive_widgets' == $widget_area ) {
551
				continue;
552
			}
553
554
			foreach ( $widgets as $position => $widget_id ) {
555
				// Find the conditions for this widget.
556
				if ( preg_match( '/^(.+?)-(\d+)$/', $widget_id, $matches ) ) {
557
					$id_base       = $matches[1];
558
					$widget_number = (int) $matches[2];
559
				} else {
560
					$id_base       = $widget_id;
561
					$widget_number = null;
562
				}
563
564
				if ( ! isset( $settings[ $id_base ] ) ) {
565
					$settings[ $id_base ] = get_option( 'widget_' . $id_base );
566
				}
567
568
				// New multi widget (WP_Widget)
569
				if ( ! is_null( $widget_number ) ) {
570
					if ( isset( $settings[ $id_base ][ $widget_number ] ) && false === self::filter_widget( $settings[ $id_base ][ $widget_number ] ) ) {
571
						unset( $widget_areas[ $widget_area ][ $position ] );
572
					}
573
				}
574
575
				// Old single widget
576
				elseif ( ! empty( $settings[ $id_base ] ) && false === self::filter_widget( $settings[ $id_base ] ) ) {
577
					unset( $widget_areas[ $widget_area ][ $position ] );
578
				}
579
			}
580
		}
581
582
		return $widget_areas;
583
	}
584
585
	public static function template_redirect() {
586
		self::$passed_template_redirect = true;
587
	}
588
589
	/**
590
	 * Generates a condition key based on the rule array
591
	 *
592
	 * @param array $rule
593
	 * @return string key used to retrieve the condition.
594
	 */
595
	static function generate_condition_key( $rule ) {
596
		if ( isset( $rule['has_children'] ) ) {
597
			return $rule['major'] . ':' . $rule['minor'] . ':' . $rule['has_children'];
598
		}
599
		return $rule['major'] . ':' . $rule['minor'];
600
	}
601
602
	/**
603
	 * Determine whether the widget should be displayed based on conditions set by the user.
604
	 *
605
	 * @param array $instance The widget settings.
606
	 * @return array Settings to display or bool false to hide.
607
	 */
608
	public static function filter_widget( $instance ) {
609
		global $wp_query;
610
611
		if ( empty( $instance['conditions'] ) || empty( $instance['conditions']['rules'] ) ) {
612
			return $instance;
613
		}
614
615
		// Store the results of all in-page condition lookups so that multiple widgets with
616
		// the same visibility conditions don't result in duplicate DB queries.
617
		static $condition_result_cache = array();
618
619
		$condition_result = false;
620
621
		foreach ( $instance['conditions']['rules'] as $rule ) {
622
			$condition_result = false;
623
			$condition_key    = self::generate_condition_key( $rule );
624
625
			if ( isset( $condition_result_cache[ $condition_key ] ) ) {
626
				$condition_result = $condition_result_cache[ $condition_key ];
627
			} else {
628
				switch ( $rule['major'] ) {
629
					case 'date':
630
						switch ( $rule['minor'] ) {
631
							case '':
632
								$condition_result = is_date();
633
								break;
634
							case 'month':
635
								$condition_result = is_month();
636
								break;
637
							case 'day':
638
								$condition_result = is_day();
639
								break;
640
							case 'year':
641
								$condition_result = is_year();
642
								break;
643
						}
644
						break;
645
					case 'page':
646
						// Previously hardcoded post type options.
647
						if ( 'post' == $rule['minor'] ) {
648
							$rule['minor'] = 'post_type-post';
649
						} elseif ( ! $rule['minor'] ) {
650
							$rule['minor'] = 'post_type-page';
651
						}
652
653
						switch ( $rule['minor'] ) {
654
							case '404':
655
								$condition_result = is_404();
656
								break;
657
							case 'search':
658
								$condition_result = is_search();
659
								break;
660
							case 'archive':
661
								$condition_result = is_archive();
662
								break;
663
							case 'posts':
664
								$condition_result = $wp_query->is_posts_page;
665
								break;
666
							case 'home':
667
								$condition_result = is_home();
668
								break;
669
							case 'front':
670
								if ( current_theme_supports( 'infinite-scroll' ) ) {
671
									$condition_result = is_front_page();
672
								} else {
673
									$condition_result = is_front_page() && ! is_paged();
674
								}
675
								break;
676
							default:
677
								if ( substr( $rule['minor'], 0, 10 ) == 'post_type-' ) {
678
									$condition_result = is_singular( substr( $rule['minor'], 10 ) );
679 View Code Duplication
								} elseif ( substr( $rule['minor'], 0, 18 ) == 'post_type_archive-' ) {
680
									$condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
681
								} elseif ( $rule['minor'] == get_option( 'page_for_posts' ) ) {
682
									// If $rule['minor'] is a page ID which is also the posts page
683
									$condition_result = $wp_query->is_posts_page;
684
								} else {
685
									// $rule['minor'] is a page ID
686
									$condition_result = is_page() && ( $rule['minor'] == get_the_ID() );
687
688
									// Check if $rule['minor'] is parent of page ID
689
									if ( ! $condition_result && isset( $rule['has_children'] ) && $rule['has_children'] ) {
690
										$condition_result = wp_get_post_parent_id( get_the_ID() ) == $rule['minor'];
691
									}
692
								}
693
								break;
694
						}
695
						break;
696 View Code Duplication
					case 'tag':
697
						// All tag pages.
698
						if ( ! $rule['minor'] ) {
699
							if ( is_tag() ) {
700
								$condition_result = true;
701
							} elseif ( is_singular() ) {
702
								if ( in_array( 'post_tag', get_post_taxonomies() ) ) {
703
									$condition_result = true;
704
								}
705
							}
706
							break;
707
						}
708
709
						// All pages with the specified tag term.
710
						if ( is_tag( $rule['minor'] ) ) {
711
							$condition_result = true;
712
						} elseif ( is_singular() && has_term( $rule['minor'], 'post_tag' ) ) {
713
							$condition_result = true;
714
						}
715
						break;
716 View Code Duplication
					case 'category':
717
						// All category pages.
718
						if ( ! $rule['minor'] ) {
719
							if ( is_category() ) {
720
								$condition_result = true;
721
							} elseif ( is_singular() ) {
722
								if ( in_array( 'category', get_post_taxonomies() ) ) {
723
									$condition_result = true;
724
								}
725
							}
726
							break;
727
						}
728
729
						// All pages with the specified category term.
730
						if ( is_category( $rule['minor'] ) ) {
731
							$condition_result = true;
732
						} elseif ( is_singular() && has_term( $rule['minor'], 'category' ) ) {
733
							$condition_result = true;
734
						}
735
						break;
736
					case 'loggedin':
737
						$condition_result = is_user_logged_in();
738
						if ( 'loggedin' !== $rule['minor'] ) {
739
							$condition_result = ! $condition_result;
740
						}
741
						break;
742
					case 'author':
743
						$post = get_post();
744
						if ( ! $rule['minor'] && is_author() ) {
745
							$condition_result = true;
746
						} elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
747
							$condition_result = true;
748
						} elseif ( is_singular() && $rule['minor'] && $rule['minor'] == $post->post_author ) {
749
							$condition_result = true;
750
						}
751
						break;
752
					case 'role':
753
						if ( is_user_logged_in() ) {
754
							$current_user = wp_get_current_user();
755
756
							$user_roles = $current_user->roles;
757
758
							if ( in_array( $rule['minor'], $user_roles ) ) {
759
								$condition_result = true;
760
							} else {
761
								$condition_result = false;
762
							}
763
						} else {
764
							$condition_result = false;
765
						}
766
						break;
767
					case 'post_type':
768
						if ( substr( $rule['minor'], 0, 10 ) == 'post_type-' ) {
769
							$condition_result = is_singular( substr( $rule['minor'], 10 ) );
770 View Code Duplication
						} elseif ( substr( $rule['minor'], 0, 18 ) == 'post_type_archive-' ) {
771
							$condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
772
						}
773
						break;
774
					case 'taxonomy':
775
						// All taxonomy pages.
776
						if ( ! $rule['minor'] ) {
777
							if ( is_archive() ) {
778
								if ( is_tag() || is_category() || is_tax() ) {
779
									$condition_result = true;
780
								}
781
							} elseif ( is_singular() ) {
782
								$post_taxonomies  = get_post_taxonomies();
783
								$condition_result = ! empty( $post_taxonomies );
784
							}
785
							break;
786
						}
787
788
						// Specified taxonomy page.
789
						$term = explode( '_tax_', $rule['minor'] ); // $term[0] = taxonomy name; $term[1] = term id
790
						if ( isset( $term[0] ) && isset( $term[1] ) ) {
791
							$term[1] = self::maybe_get_split_term( $term[1], $term[0] );
792
						}
793
794
						// All pages of the specified taxonomy.
795
						if ( ! isset( $term[1] ) || ! $term[1] ) {
796
							if ( is_tax( $term[0] ) ) {
797
								$condition_result = true;
798
							} elseif ( is_singular() ) {
799
								if ( in_array( $term[0], get_post_taxonomies() ) ) {
800
									$condition_result = true;
801
								}
802
							}
803
							break;
804
						}
805
806
						// All pages with the specified taxonomy term.
807
						if ( is_tax( $term[0], $term[1] ) ) {
808
							$condition_result = true;
809
						} elseif ( is_singular() && has_term( $term[1], $term[0] ) ) {
810
							$condition_result = true;
811
						}
812
						break;
813
				}
814
815
				if ( $condition_result || self::$passed_template_redirect ) {
816
					// Some of the conditions will return false when checked before the template_redirect
817
					// action has been called, like is_page(). Only store positive lookup results, which
818
					// won't be false positives, before template_redirect, and everything after.
819
					$condition_result_cache[ $condition_key ] = $condition_result;
820
				}
821
			}
822
823
			if (
824
				isset( $instance['conditions']['match_all'] )
825
				&& $instance['conditions']['match_all'] == '1'
826
				&& ! $condition_result
827
			) {
828
829
				// In case the match_all flag was set we quit on first failed condition
830
				break;
831
			} elseif (
832
				(
833
					empty( $instance['conditions']['match_all'] )
834
					|| $instance['conditions']['match_all'] !== '1'
835
				)
836
				&& $condition_result
837
			) {
838
839
				// Only quit on first condition if the match_all flag was not set
840
				break;
841
			}
842
		}
843
844
		if (
845
			(
846
				'show' == $instance['conditions']['action']
847
				&& ! $condition_result
848
			) || (
849
				'hide' == $instance['conditions']['action']
850
				&& $condition_result
851
			)
852
		) {
853
			return false;
854
		}
855
856
		return $instance;
857
	}
858
859
	public static function strcasecmp_name( $a, $b ) {
860
		return strcasecmp( $a->name, $b->name );
861
	}
862
863
	public static function maybe_get_split_term( $old_term_id = '', $taxonomy = '' ) {
864
		$term_id = $old_term_id;
865
866
		if ( 'tag' == $taxonomy ) {
867
			$taxonomy = 'post_tag';
868
		}
869
870
		if ( $new_term_id = wp_get_split_term( $old_term_id, $taxonomy ) ) {
871
			$term_id = $new_term_id;
872
		}
873
874
		return $term_id;
875
	}
876
877
	/**
878
	 * Upgrade routine to go through all widgets and move the Post Type
879
	 * setting to its newer location.
880
	 *
881
	 * @since 4.7.1
882
	 */
883
	static function migrate_post_type_rules() {
884
		global $wp_registered_widgets;
885
886
		$sidebars_widgets = get_option( 'sidebars_widgets' );
887
888
		// Going through all sidebars and through inactive and orphaned widgets
889
		foreach ( $sidebars_widgets as $s => $sidebar ) {
890
			if ( ! is_array( $sidebar ) ) {
891
				continue;
892
			}
893
894
			foreach ( $sidebar as $w => $widget ) {
895
				// $widget is the id of the widget
896
				if ( empty( $wp_registered_widgets[ $widget ] ) ) {
897
					continue;
898
				}
899
900
				$opts      = $wp_registered_widgets[ $widget ];
901
				$instances = get_option( $opts['callback'][0]->option_name );
902
903
				// Going through each instance of the widget
904
				foreach ( $instances as $number => $instance ) {
905
					if (
906
						! is_array( $instance ) ||
907
						empty( $instance['conditions'] ) ||
908
						empty( $instance['conditions']['rules'] )
909
					) {
910
						continue;
911
					}
912
913
					// Going through all visibility rules
914
					foreach ( $instance['conditions']['rules'] as $index => $rule ) {
915
916
						// We only need Post Type rules
917
						if ( 'post_type' !== $rule['major'] ) {
918
							continue;
919
						}
920
921
						$rule_type = false;
922
923
						// Post type or type archive rule
924
						if ( 0 === strpos( $rule['minor'], 'post_type_archive' ) ) {
925
							$rule_type = 'post_type_archive';
926
						} elseif ( 0 === strpos( $rule['minor'], 'post_type' ) ) {
927
							$rule_type = 'post_type';
928
						}
929
930
						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...
931
							$post_type     = substr( $rule['minor'], strlen( $rule_type ) + 1 );
932
							$rule['minor'] = $rule_type . '-' . $post_type;
933
							$rule['major'] = 'page';
934
935
							$instances[ $number ]['conditions']['rules'][ $index ] = $rule;
936
						}
937
					}
938
				}
939
940
				update_option( $opts['callback'][0]->option_name, $instances );
941
			}
942
		}
943
	}
944
945
}
946
947
add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
948