Completed
Push — infinite-scroll/434 ( c543ef )
by George
111:14 queued 101:46
created

widget_conditions_options_echo()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
nc 3
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
4
/**
5
 * Hide or show widgets conditionally.
6
 */
7
8
class Jetpack_Widget_Conditions {
9
	static $passed_template_redirect = false;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $passed_template_redirect.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
10
11
	public static function init() {
12
		if ( is_admin() ) {
13
			add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
14
			add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
15
			add_action( 'in_widget_form', array( __CLASS__, 'widget_conditions_admin' ), 10, 3 );
16
		} else if ( ! in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) ) {
17
			add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
18
			add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
19
			add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ) );
20
		}
21
22
		/**
23
		 * The widget_visibility_major_conditions filter accepts an array of existing conditions
24
		 * and should add new conditions in this format:
25
		 *
26
		 * $existing_conditions['my_unique_key'] = array(
27
		 *     'title' => 'User-facing label for this condition type',
28
		 *     'callback' => callable that accepts two arguments: a major condition (e.g., 'my_unique_key')
29
		 *                   and optional minor value (e.g., 'one_of_several_values')
30
		 */
31
		add_filter( 'widget_visibility_major_conditions', array( __CLASS__, 'major_conditions' ) );
32
33
		/**
34
		 * The widget_visibility_minor_conditions filter accepts two arguments: a major condition (e.g.,
35
		 * 'my_unique_key') and an array of exisiting minor values. It should add new minor conditions in
36
		 * this format:
37
		 *
38
		 * $existing_minor_conditions['a_unique_key'] = 'User-facing label for this condition type'
39
		 */
40
		add_filter( 'widget_visibility_minor_conditions', array( __CLASS__, 'minor_conditions' ), 10, 2 );
41
	}
42
43
	public static function widget_admin_setup() {
44
		if( is_rtl() ) {
45
			wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/rtl/widget-conditions-rtl.css', __FILE__ ) );
46
		} else {
47
			wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ) );
48
		}
49
		wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ) );
50
		wp_enqueue_script( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.js', __FILE__ ), array( 'jquery', 'jquery-ui-core' ), 20140721, true );
51
52
		// Set up a single copy of all of the data that Widget Visibility needs.
53
		// This allows all widget conditions to reuse the same data, keeping page size down
54
		// and eliminating the AJAX calls we used to have to use to fetch the minor rule options.
55
		$widget_conditions_data = array();
56
57
		$widget_conditions_data['category'] = array();
58
		$widget_conditions_data['category'][] = array( '', __( 'All category pages', 'jetpack' ) );
59
60
		$categories = get_categories( array( 'number' => 1000, 'orderby' => 'count', 'order' => 'DESC' ) );
61
		usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
62
63
		foreach ( $categories as $category ) {
64
			$widget_conditions_data['category'][] = array( (string) $category->term_id, $category->name );
65
		}
66
67
		$widget_conditions_data['loggedin'] = array();
68
		$widget_conditions_data['loggedin'][] = array( 'loggedin', __( 'Logged In', 'jetpack' ) );
69
		$widget_conditions_data['loggedin'][] = array( 'loggedout', __( 'Logged Out', 'jetpack' ) );
70
71
		$widget_conditions_data['author'] = array();
72
		$widget_conditions_data['author'][] = array( '', __( 'All author pages', 'jetpack' ) );
73
74
		$authors = get_users( array( 'orderby' => 'name', 'exclude_admin' => true ) );
75
76
		foreach ( $authors as $author ) {
77
			$widget_conditions_data['author'][] = array( (string) $author->ID, $author->display_name );
78
		}
79
80
		$widget_conditions_data['role'] = array();
81
82
		global $wp_roles;
83
84
		foreach ( $wp_roles->roles as $role_key => $role ) {
85
			$widget_conditions_data['role'][] = array( (string) $role_key, $role['name'] );
86
		}
87
88
		$widget_conditions_data['tag'] = array();
89
		$widget_conditions_data['tag'][] = array( '', __( 'All tag pages', 'jetpack' ) );
90
91
		$tags = get_tags( array( 'number' => 1000, 'orderby' => 'count', 'order' => 'DESC' ) );
92
		usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
93
94
		foreach ( $tags as $tag ) {
95
			$widget_conditions_data['tag'][] = array( (string) $tag->term_id, $tag->name );
96
		}
97
98
		$widget_conditions_data['date'] = array();
99
		$widget_conditions_data['date'][] = array( '', __( 'All date archives', 'jetpack' ) );
100
		$widget_conditions_data['date'][] = array( 'day', __( 'Daily archives', 'jetpack' ) );
101
		$widget_conditions_data['date'][] = array( 'month', __( 'Monthly archives', 'jetpack' ) );
102
		$widget_conditions_data['date'][] = array( 'year', __( 'Yearly archives', 'jetpack' ) );
103
104
		$widget_conditions_data['page'] = array();
105
		$widget_conditions_data['page'][] = array( 'front', __( 'Front page', 'jetpack' ) );
106
		$widget_conditions_data['page'][] = array( 'posts', __( 'Posts page', 'jetpack' ) );
107
		$widget_conditions_data['page'][] = array( 'archive', __( 'Archive page', 'jetpack' ) );
108
		$widget_conditions_data['page'][] = array( '404', __( '404 error page', 'jetpack' ) );
109
		$widget_conditions_data['page'][] = array( 'search', __( 'Search results', 'jetpack' ) );
110
111
		$post_types = get_post_types( array( 'public' => true ), 'objects' );
112
113
		$widget_conditions_post_types = array();
114
115
		foreach ( $post_types as $post_type ) {
116
			$widget_conditions_post_types[] = array( 'post_type-' . $post_type->name, $post_type->labels->singular_name );
117
		}
118
119
		$widget_conditions_data['page'][] = array( __( 'Post type:', 'jetpack' ), $widget_conditions_post_types );
120
121
		$pages_dropdown = preg_replace( '/<\/?select[^>]*?>/i', '', wp_dropdown_pages( array( 'echo' => false ) ) );
122
123
		preg_match_all( '/value=.([0-9]+).[^>]*>([^<]+)</', $pages_dropdown, $page_ids_and_titles, PREG_SET_ORDER );
124
125
		$static_pages = array();
126
127
		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...
128
			$static_pages[] = array( (string) $page_id_and_title[1], $page_id_and_title[2] );
129
		}
130
131
		$widget_conditions_data['page'][] = array( __( 'Static page:', 'jetpack' ), $static_pages );
132
133
		$widget_conditions_data['taxonomy'] = array();
134
		$widget_conditions_data['taxonomy'][] = array( '', __( 'All taxonomy pages', 'jetpack' ) );
135
136
		$taxonomies = get_taxonomies( array( '_builtin' => false ), 'objects' );
137
		usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
138
139
		foreach ( $taxonomies as $taxonomy ) {
140
			$taxonomy_terms = get_terms( array( $taxonomy->name ), array( 'number' => 250, 'hide_empty' => false ) );
141
142
			$widget_conditions_terms = array();
143
			$widget_conditions_terms[] = array( $taxonomy->name, __( 'All pages', 'jetpack' ) );
144
145
			foreach ( $taxonomy_terms as $term ) {
146
				$widget_conditions_terms[] = array( $taxonomy->name . '_tax_' . $term->term_id, $term->name );
147
			}
148
149
			$widget_conditions_data['taxonomy'][] = array( $taxonomy->labels->name . ':', $widget_conditions_terms );
150
		}
151
152
		wp_localize_script( 'widget-conditions', 'widget_conditions_data', $widget_conditions_data );
153
154
		// Save a list of the IDs of all pages that have children for dynamically showing the "Include children" checkbox.
155
		$all_pages = get_pages();
156
		$all_parents = array();
157
158
		foreach ( $all_pages as $page ) {
159
			if ( $page->post_parent ) {
160
				$all_parents[ (string) $page->post_parent ] = true;
161
			}
162
		}
163
164
		$front_page_id = get_option( 'page_on_front' );
165
166
		if ( isset( $all_parents[ $front_page_id ] ) ) {
167
			$all_parents[ 'front' ] = true;
168
		}
169
170
		wp_localize_script( 'widget-conditions', 'widget_conditions_parent_pages', $all_parents );
171
	}
172
173
	/**
174
	 * Register the major condition types that we'll support by default.
175
	 */
176
	public static function major_conditions( $major_conditions ) {
177
		return array_merge( $major_conditions, array(
178
			'category' => array(
179
				'title' => __( 'Category', 'jetpack' ),
180
				'callback' => array( __CLASS__, 'condition_callback' ),
181
			),
182
			'author' => array(
183
				'title' => _x( 'Author', 'Noun, as in: "The author of this post is..."', 'jetpack' ),
184
				'callback' => array( __CLASS__, 'condition_callback' ),
185
			),
186
			'tag' => array(
187
				'title' => _x( 'Tag', 'Noun, as in: "This post has one tag."', 'jetpack' ),
188
				'callback' => array( __CLASS__, 'condition_callback' ),
189
			),
190
			'date' => array(
191
				'title' => _x( 'Date', 'Noun, as in: "This page is a date archive."', 'jetpack' ),
192
				'callback' => array( __CLASS__, 'condition_callback' ),
193
			),
194
			'page' => array(
195
				'title' => _x( 'Page', 'Example: The user is looking at a page, not a post.', 'jetpack' ),
196
				'callback' => array( __CLASS__, 'condition_callback' ),
197
			),
198
			'taxonomy' => array(
199
				'title' => _x( 'Taxonomy', 'Noun, as in: "This post has one taxonomy."', 'jetpack' ),
200
				'callback' => array( __CLASS__, 'condition_callback' ),
201
			),
202
		) );
203
	}
204
205
	/**
206
	 * Register the possible values for the major conditions that we support.
207
	 *
208
	 * This takes `$minor_conditions` as the first parameter as it's a filter
209
	 * on that value.
210
	 *
211
	 * @param array $minor_conditions An associative array of possible values.
212
	 * @param string $major_condition The condition for these values.
213
	 * @return array
214
	 */
215
	public static function minor_conditions( $minor_conditions, $major_condition ) {
216
		switch ( $major_condition ) {
217 View Code Duplication
			case 'category':
218
				$minor_conditions[''] = __( 'All category pages', 'jetpack' );
219
220
				$categories = get_categories( array( 'number' => 1000, 'orderby' => 'count', 'order' => 'DESC' ) );
221
				usort( $categories, array( __CLASS__, 'strcasecmp_name' ) );
222
223
				foreach ( $categories as $category ) {
224
					$minor_conditions[$category->term_id] = $category->name;
225
				}
226
			break;
227
			case 'author':
228
				$minor_conditions[''] = __( 'All author pages', 'jetpack' );
229
230
				foreach ( get_users( array( 'orderby' => 'name', 'exclude_admin' => true ) ) as $author ) {
231
					$minor_conditions[$author->ID] = $author->display_name;
232
				}
233
			break;
234 View Code Duplication
			case 'tag':
235
				$minor_conditions[''] = __( 'All tag pages', 'jetpack' );
236
237
				$tags = get_tags( array( 'number' => 1000, 'orderby' => 'count', 'order' => 'DESC' ) );
238
				usort( $tags, array( __CLASS__, 'strcasecmp_name' ) );
239
240
				foreach ( $tags as $tag ) {
241
					$minor_conditions[$tag->term_id] = $tag->name;
242
				}
243
			break;
244
			case 'date':
245
				$minor_conditions[''] = __( 'All date archives', 'jetpack' );
246
				$minor_conditions['day'] = __( 'Daily archives', 'jetpack' );
247
				$minor_conditions['month'] = __( 'Monthly archives', 'jetpack' );
248
				$minor_conditions['year'] = __( 'Yearly archives', 'jetpack' );
249
			break;
250
			case 'page':
251
				$minor_conditions['front'] = __( 'Front page', 'jetpack' );
252
				$minor_conditions['posts'] = __( 'Posts page', 'jetpack' );
253
				$minor_conditions['archive'] = __( 'Archive page', 'jetpack' );
254
				$minor_conditions['404'] = __( '404 error page', 'jetpack' );
255
				$minor_conditions['search'] = __( 'Search results', 'jetpack' );
256
257
				$post_type_values = array();
258
				$post_types = get_post_types( array( 'public' => true ), 'objects' );
259
260
				foreach ( $post_types as $post_type ) {
261
					$post_type_values[ 'post_type-' . $post_type->name ] = $post_type->labels->singular_name;
262
				}
263
264
				$minor_conditions[ __( 'Post type:', 'jetpack' ) ] = $post_type_values;
265
266
				$static_pages = array();
267
268
				$pages = get_pages( array( 'sort_order' => 'menu_order' ) );
269
				$parent_depths = array();
270
271
				foreach ( $pages as $page ) {
272
					if ( isset( $parent_depths[ $page->post_parent ] ) ) {
273
						$depth = $parent_depths[ $page->post_parent ] + 1;
274
					}
275
					else {
276
						$depth = 0;
277
					}
278
279
					$parent_depths[ $page->ID ] = $depth;
280
281
					$static_pages[ $page->ID ] = str_repeat( "&nbsp;", $depth * 2 ) . $page->post_title;
282
				}
283
284
				$minor_conditions[ __( 'Static page:', 'jetpack' ) ] = $static_pages;
285
			break;
286
			case 'taxonomy':
287
				$minor_conditions[''] = __( 'All taxonomy pages', 'jetpack' );
288
289
				$taxonomies = get_taxonomies( array( '_builtin' => false ), 'objects' );
290
				usort( $taxonomies, array( __CLASS__, 'strcasecmp_name' ) );
291
292
				foreach ( $taxonomies as $taxonomy ) {
293
					$sub_taxonomies = array(
294
						$taxonomy->name => sprintf( __( 'All %s pages', 'jetpack' ), $taxonomy->name ),
295
					);
296
297
					$terms = get_terms( array( $taxonomy->name ), array( 'number' => 1000, 'hide_empty' => false ) );
298
					foreach ( $terms as $term ) {
299
						$sub_taxonomies[ $taxonomy->name . '_tax_' . $term->term_id ] = $term->name;
300
					}
301
302
					$minor_conditions[ $taxonomy->labels->name . ':' ] = $sub_taxonomies;
303
				}
304
			break;
305
		}
306
307
		return $minor_conditions;
308
	}
309
310
	/**
311
	 * The callback that does the actual filtering.
312
	 *
313
	 * @param string $major The "major" filter category.
314
	 * @param string $minor The "minor" filter value.
315
	 * @return boolean Whether this condition is met in the current context.
316
	 */
317
	public static function condition_callback( $major, $minor ) {
318
		switch ( $major ) {
319
			case 'date':
320
				switch ( $minor ) {
321
					case '':
322
						return is_date();
323
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
324
					case 'month':
325
						return is_month();
326
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
327
					case 'day':
328
						return is_day();
329
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
330
					case 'year':
331
						return is_year();
332
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
333
				}
334
			break;
335
			case 'page':
336
				// Previously hardcoded post type options.
337
				if ( 'post' == $minor )
338
					$minor = 'post_type-post';
339
				else if ( ! $minor )
340
					$minor = 'post_type-page';
341
342
				switch ( $minor ) {
343
					case '404':
344
						return is_404();
345
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
346
					case 'search':
347
						return is_search();
348
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
349
					case 'archive':
350
						return is_archive();
351
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
352
					case 'posts':
353
						return $wp_query->is_posts_page;
0 ignored issues
show
Bug introduced by
The variable $wp_query does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
354
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
355
					case 'home':
356
						return is_home();
357
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
358
					case 'front':
359
						if ( current_theme_supports( 'infinite-scroll' ) )
360
							return is_front_page();
361
						else {
362
							return is_front_page() && !is_paged();
363
						}
364
					break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
365
					default:
366
						if ( substr( $minor, 0, 10 ) == 'post_type-' )
367
							return is_singular( substr( $minor, 10 ) );
368
						else {
369
							// $minor is a page ID -- check if we're either looking at that particular page itself OR looking at the posts page, with the correct conditions
370
							return ( is_page( $minor ) || ( get_option( 'show_on_front' ) == 'page' && $wp_query->is_posts_page && get_option( 'page_for_posts' ) == $minor ) );
371
						}
372
					break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
373
				}
374
			break;
375
			case 'tag':
376
				if ( ! $minor && is_tag() )
377
					return true;
378
				else if ( is_singular() && $minor && has_tag( $minor ) )
379
					return true;
380
				else {
381
					$tag = get_tag( $minor );
382
383
					if ( $tag && is_tag( $tag->slug ) )
384
						return true;
385
				}
386
			break;
387
			case 'category':
388
				if ( ! $minor && is_category() )
389
					return true;
390
				else if ( is_category( $minor ) )
391
					return true;
392
				else if ( is_singular() && $minor && in_array( 'category', get_post_taxonomies() ) &&  has_category( $minor ) )
393
					return true;
394
			break;
395
			case 'author':
396
				if ( ! $minor && is_author() )
397
					return true;
398
				else if ( $minor && is_author( $minor ) )
399
					return true;
400
				else if ( is_singular() && $minor && $minor == $post->post_author )
0 ignored issues
show
Bug introduced by
The variable $post does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
401
					return true;
402
			break;
403
			case 'taxonomy':
404
				$term = explode( '_tax_', $minor ); // $term[0] = taxonomy name; $term[1] = term id
405
				$terms = get_the_terms( $post->ID, $minor ); // Does post have terms in taxonomy?
406
				if ( is_tax( $term[0], $term[1] ) )
407
					return true;
408
				else if ( is_singular() && $term[1] && has_term( $term[1], $term[0] ) )
409
					return true;
410
				else if ( is_singular() && $terms & !is_wp_error( $terms ) )
411
					return true;
412
			break;
413
		}
414
415
		return false;
416
	}
417
418
	/**
419
	 * Provided a second level of granularity for widget conditions.
420
	 */
421
	public static function widget_conditions_options_echo( $major = '', $selected_value = '' ) {
422
		if ( $major ) {
423
			$minor_conditions = apply_filters( 'widget_visibility_minor_conditions', array(), $major );
424
425
			foreach ( $minor_conditions as $key => $val ) {
426
				self::do_widget_conditions_options_echo( $selected_value, $key, $val );
427
			}
428
		}
429
	}
430
431
	private static function do_widget_conditions_options_echo( $selected_value, $minor_key, $minor_value ) {
432
		if ( is_array( $minor_value ) ) {
433
			?>
434
			<optgroup label="<?php echo esc_attr( $minor_key ); ?>">
435
				<?php
436
437
				foreach ( $minor_value as $grouped_minor_key => $grouped_minor_value ) {
438
					self::do_widget_conditions_options_echo( $selected_value, $grouped_minor_key, $grouped_minor_value );
439
				}
440
441
				?>
442
			</optgroup>
443
			<?php
444
		}
445
		else {
446
			?>
447
			<option value="<?php echo esc_attr( $minor_key ); ?>" <?php selected( $minor_key, $selected_value ); ?>><?php echo esc_html( $minor_value ); ?></option>
448
			<?php
449
		}
450
	}
451
452
	/**
453
	 * Add the widget conditions to each widget in the admin.
454
	 *
455
	 * @param $widget unused.
456
	 * @param $return unused.
457
	 * @param array $instance The widget settings.
458
	 */
459
	public static function widget_conditions_admin( $widget, $return, $instance ) {
460
		$conditions = array();
461
462
		if ( isset( $instance['conditions'] ) )
463
			$conditions = $instance['conditions'];
464
465
		if ( ! isset( $conditions['action'] ) )
466
			$conditions['action'] = 'show';
467
468
		if ( empty( $conditions['rules'] ) )
469
			$conditions['rules'][] = array( 'major' => '', 'minor' => '', 'has_children' => '' );
470
471
		$major_conditions = apply_filters( 'widget_visibility_major_conditions', array() );
472
473
		?>
474
		<div class="widget-conditional <?php if ( empty( $_POST['widget-conditions-visible'] ) || $_POST['widget-conditions-visible'] == '0' ) { ?>widget-conditional-hide<?php } ?>">
475
			<input type="hidden" name="widget-conditions-visible" value="<?php if ( isset( $_POST['widget-conditions-visible'] ) ) { echo esc_attr( $_POST['widget-conditions-visible'] ); } else { ?>0<?php } ?>" />
476
			<?php if ( ! isset( $_POST['widget-conditions-visible'] ) ) { ?><a href="#" class="button display-options"><?php _e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
477
			<div class="widget-conditional-inner">
478
				<div class="condition-top">
479
					<?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>' ); ?>
480
				</div><!-- .condition-top -->
481
482
				<div class="conditions">
483
					<?php
484
485
					foreach ( $conditions['rules'] as $rule_index => $rule ) {
486
						$rule = wp_parse_args( $rule, array( 'major' => '', 'minor' => '', 'has_children' => '' ) );
487
						?>
488
						<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'] ); ?>">
489
							<div class="selection alignleft">
490
								<select class="conditions-rule-major" name="conditions[rules_major][]">
491
									<option value="" <?php selected( "", $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
492
									<?php foreach ( $major_conditions as $major_condition_key => $major_condition_meta ) { ?>
493
										<option value="<?php echo esc_attr( $major_condition_key ); ?>" <?php selected( $major_condition_key, $rule['major'] ); ?>><?php echo esc_html( $major_condition_meta['title'] ); ?></option>
494
									<?php } ?>
495
								</select>
496
497
								<?php _ex( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>
498
499
								<select class="conditions-rule-minor" name="conditions[rules_minor][]" <?php if ( ! $rule['major'] ) { ?> disabled="disabled"<?php } ?>>
500
									<?php /* Include the currently selected value so that if the widget is saved without
501
									         expanding the Visibility section, we don't lose the minor part of the rule.
502
									         If it is opened, this list is cleared out and populated with all the values. */ ?>
503
									<option value="<?php echo esc_attr( $rule['minor'] ); ?>" selected="selected"></option>
504
								</select>
505
506
								<span class="conditions-rule-has-children" <?php if ( ! $rule['has_children'] ) { ?> style="display: none;"<?php } ?>>
507
									<label>
508
										<input type="checkbox" name="conditions[page_children][<?php echo $rule_index; ?>]" value="has" <?php checked( $rule['has_children'], true ); ?> />
509
										<?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' ); ?>
510
									</label>
511
								</span>
512
							</div>
513
514
							<div class="condition-control">
515
								<span class="condition-conjunction"><?php echo esc_html_x( 'or', 'Shown between widget visibility conditions.', 'jetpack' ); ?></span>
516
								<div class="actions alignright">
517
									<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>
518
								</div>
519
							</div>
520
521
						</div><!-- .condition -->
522
						<?php
523
					}
524
525
					?>
526
				</div><!-- .conditions -->
527
			</div><!-- .widget-conditional-inner -->
528
		</div><!-- .widget-conditional -->
529
		<?php
530
	}
531
532
	/**
533
	 * On an AJAX update of the widget settings, process the display conditions.
534
	 *
535
	 * @param array $new_instance New settings for this instance as input by the user.
536
	 * @param array $old_instance Old settings for this instance.
537
	 * @return array Modified settings.
538
	 */
539
	public static function widget_update( $instance, $new_instance, $old_instance ) {
540
		if ( empty( $_POST['conditions'] ) ) {
541
			return $instance;
542
		}
543
544
		$conditions = array();
545
		$conditions['action'] = $_POST['conditions']['action'];
546
		$conditions['rules'] = array();
547
548
		foreach ( $_POST['conditions']['rules_major'] as $index => $major_rule ) {
549
			if ( ! $major_rule )
550
				continue;
551
552
			$conditions['rules'][] = array(
553
				'major' => $major_rule,
554
				'minor' => isset( $_POST['conditions']['rules_minor'][$index] ) ? $_POST['conditions']['rules_minor'][$index] : '',
555
				'has_children' => isset( $_POST['conditions']['page_children'][$index] ) ? true : false,
556
			);
557
		}
558
559
		if ( ! empty( $conditions['rules'] ) )
560
			$instance['conditions'] = $conditions;
561
		else
562
			unset( $instance['conditions'] );
563
564
		if (
565
				( isset( $instance['conditions'] ) && ! isset( $old_instance['conditions'] ) )
566
				||
567
				(
568
					isset( $instance['conditions'], $old_instance['conditions'] )
569
					&&
570
					serialize( $instance['conditions'] ) != serialize( $old_instance['conditions'] )
571
				)
572
			) {
573
574
			/**
575
			 * Fires after the widget visibility conditions are saved.
576
			 *
577
			 * @module widget-visibility
578
			 *
579
			 * @since 2.4.0
580
			 */
581
			do_action( 'widget_conditions_save' );
582
		}
583
		else if ( ! isset( $instance['conditions'] ) && isset( $old_instance['conditions'] ) ) {
584
585
			/**
586
			 * Fires after the widget visibility conditions are deleted.
587
			 *
588
			 * @module widget-visibility
589
			 *
590
			 * @since 2.4.0
591
			 */
592
			do_action( 'widget_conditions_delete' );
593
		}
594
595
		return $instance;
596
	}
597
598
	/**
599
	 * Filter the list of widgets for a sidebar so that active sidebars work as expected.
600
	 *
601
	 * @param array $widget_areas An array of widget areas and their widgets.
602
	 * @return array The modified $widget_area array.
603
	 */
604
	public static function sidebars_widgets( $widget_areas ) {
605
		$settings = array();
606
607
		foreach ( $widget_areas as $widget_area => $widgets ) {
608
			if ( empty( $widgets ) )
609
				continue;
610
611
			if ( ! is_array( $widgets ) )
612
				continue;
613
614
			if ( 'wp_inactive_widgets' == $widget_area )
615
				continue;
616
617
			foreach ( $widgets as $position => $widget_id ) {
618
				// Find the conditions for this widget.
619
				if ( preg_match( '/^(.+?)-(\d+)$/', $widget_id, $matches ) ) {
620
					$id_base = $matches[1];
621
					$widget_number = intval( $matches[2] );
622
				}
623
				else {
624
					$id_base = $widget_id;
625
					$widget_number = null;
626
				}
627
628
				if ( ! isset( $settings[$id_base] ) ) {
629
					$settings[$id_base] = get_option( 'widget_' . $id_base );
630
				}
631
632
				// New multi widget (WP_Widget)
633
				if ( ! is_null( $widget_number ) ) {
634
					if ( isset( $settings[$id_base][$widget_number] ) && false === self::filter_widget( $settings[$id_base][$widget_number] ) ) {
635
						unset( $widget_areas[$widget_area][$position] );
636
					}
637
				}
638
639
				// Old single widget
640
				else if ( ! empty( $settings[ $id_base ] ) && false === self::filter_widget( $settings[$id_base] ) ) {
641
					unset( $widget_areas[$widget_area][$position] );
642
				}
643
			}
644
		}
645
646
		return $widget_areas;
647
	}
648
649
	public static function template_redirect() {
650
		self::$passed_template_redirect = true;
651
	}
652
653
	/**
654
	 * Generates a condition key based on the rule array
655
	 *
656
	 * @param array $rule
657
	 * @return string key used to retrieve the condition.
658
	 */
659
	static function generate_condition_key( $rule ) {
660
		if ( isset( $rule['has_children'] ) ) {
661
			return $rule['major'] . ":" . $rule['minor'] . ":" . $rule['has_children'];
662
		}
663
		return $rule['major'] . ":" . $rule['minor'];
664
	}
665
666
	/**
667
	 * Determine whether the widget should be displayed based on conditions set by the user.
668
	 *
669
	 * @param array $instance The widget settings.
670
	 * @return array Settings to display or bool false to hide.
671
	 */
672
	public static function filter_widget( $instance ) {
673
		global $wp_query;
674
675
		if ( empty( $instance['conditions'] ) || empty( $instance['conditions']['rules'] ) )
676
			return $instance;
677
678
		// Store the results of all in-page condition lookups so that multiple widgets with
679
		// the same visibility conditions don't result in duplicate DB queries.
680
		static $condition_result_cache = array();
681
682
		$condition_result = false;
683
684
		$major_conditions = apply_filters( 'widget_visibility_major_conditions', array() );
685
		foreach ( $instance['conditions']['rules'] as $rule ) {
686
			if ( isset( $major_conditions[ $rule['major'] ] ) ) {
687
				$condition_result = call_user_func( $major_conditions[ $rule['major'] ]['callback'], $rule['major'], $rule['minor'] );
688
			}
689
690
			if ( $condition_result )
691
				break;
692
		}
693
694
		if ( ( 'show' == $instance['conditions']['action'] && ! $condition_result ) || ( 'hide' == $instance['conditions']['action'] && $condition_result ) )
695
			return false;
696
697
		return $instance;
698
	}
699
700
	public static function strcasecmp_name( $a, $b ) {
701
		return strcasecmp( $a->name, $b->name );
702
	}
703
704
	public static function maybe_get_split_term( $old_term_id = '', $taxonomy = '' ) {
705
		$term_id = $old_term_id;
706
707
		if ( 'tag' == $taxonomy ) {
708
			$taxonomy = 'post_tag';
709
		}
710
711
		if ( function_exists( 'wp_get_split_term' ) && $new_term_id = wp_get_split_term( $old_term_id, $taxonomy ) ) {
712
			$term_id = $new_term_id;
713
		}
714
715
		return $term_id;
716
	}
717
}
718
719
add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
720