Completed
Push — update/opentable-embed-url ( 211c30 )
by
unknown
21:15 queued 14:06
created

Jetpack_Search_Widget::widget_instant()   C

Complexity

Conditions 11
Paths 132

Size

Total Lines 72

Duplication

Lines 3
Ratio 4.17 %

Importance

Changes 0
Metric Value
cc 11
nc 132
nop 2
dl 3
loc 72
rs 6.2509
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Jetpack_Search_Widget::sorting_to_wp_query_param() 0 12 5

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
 * Jetpack Search: Jetpack_Search_Widget class
4
 *
5
 * @package    Jetpack
6
 * @subpackage Jetpack Search
7
 * @since      5.0.0
8
 */
9
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Status;
12
13
add_action( 'widgets_init', 'jetpack_search_widget_init' );
14
15
function jetpack_search_widget_init() {
16
	if (
17
		! Jetpack::is_active()
18
		|| ( method_exists( 'Jetpack_Plan', 'supports' ) && ! Jetpack_Plan::supports( 'search' ) )
19
	) {
20
		return;
21
	}
22
23
	require_once JETPACK__PLUGIN_DIR . 'modules/search/class.jetpack-search-helpers.php';
24
25
	register_widget( 'Jetpack_Search_Widget' );
26
}
27
28
/**
29
 * Provides a widget to show available/selected filters on searches.
30
 *
31
 * @since 5.0.0
32
 *
33
 * @see   WP_Widget
34
 */
35
class Jetpack_Search_Widget extends WP_Widget {
36
37
	/**
38
	 * The Jetpack_Search instance.
39
	 *
40
	 * @since 5.7.0
41
	 * @var Jetpack_Search
42
	 */
43
	protected $jetpack_search;
44
45
	/**
46
	 * Number of aggregations (filters) to show by default.
47
	 *
48
	 * @since 5.8.0
49
	 * @var int
50
	 */
51
	const DEFAULT_FILTER_COUNT = 5;
52
53
	/**
54
	 * Default sort order for search results.
55
	 *
56
	 * @since 5.8.0
57
	 * @var string
58
	 */
59
	const DEFAULT_SORT = 'relevance_desc';
60
61
	/**
62
	 * Jetpack_Search_Widget constructor.
63
	 *
64
	 * @since 5.0.0
65
	 */
66
	public function __construct( $name = null ) {
67
		if ( empty( $name ) ) {
68
			$name = esc_html__( 'Search', 'jetpack' );
69
		}
70
		parent::__construct(
71
			Jetpack_Search_Helpers::FILTER_WIDGET_BASE,
72
			/** This filter is documented in modules/widgets/facebook-likebox.php */
73
			apply_filters( 'jetpack_widget_name', $name ),
74
			array(
75
				'classname'   => 'jetpack-filters widget_search',
76
				'description' => __( 'Replaces the default search with an Elasticsearch-powered search interface and filters.', 'jetpack' ),
77
			)
78
		);
79
80
		if (
81
			Jetpack_Search_Helpers::is_active_widget( $this->id ) &&
82
			! $this->is_search_active()
83
		) {
84
			$this->activate_search();
85
		}
86
87
		if ( is_admin() ) {
88
			add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
89
		} else {
90
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
91
		}
92
93
		add_action( 'jetpack_search_render_filters_widget_title', array( 'Jetpack_Search_Template_Tags', 'render_widget_title' ), 10, 3 );
94
		add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_available_filters' ), 10, 2 );
95
	}
96
97
	/**
98
	 * Check whether search is currently active
99
	 *
100
	 * @since 6.3
101
	 */
102
	public function is_search_active() {
103
		return Jetpack::is_module_active( 'search' );
104
	}
105
106
	/**
107
	 * Activate search
108
	 *
109
	 * @since 6.3
110
	 */
111
	public function activate_search() {
112
		Jetpack::activate_module( 'search', false, false );
113
	}
114
115
116
	/**
117
	 * Enqueues the scripts and styles needed for the customizer.
118
	 *
119
	 * @since 5.7.0
120
	 */
121
	public function widget_admin_setup() {
122
		wp_enqueue_style( 'widget-jetpack-search-filters', plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ) );
123
124
		// Required for Tracks
125
		wp_register_script(
126
			'jp-tracks',
127
			'//stats.wp.com/w.js',
128
			array(),
129
			gmdate( 'YW' ),
130
			true
131
		);
132
133
		wp_register_script(
134
			'jp-tracks-functions',
135
			plugins_url( '_inc/lib/tracks/tracks-callables.js', JETPACK__PLUGIN_FILE ),
136
			array(),
137
			JETPACK__VERSION,
138
			false
139
		);
140
141
		wp_register_script(
142
			'jetpack-search-widget-admin',
143
			plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
144
			array( 'jquery', 'jquery-ui-sortable', 'jp-tracks', 'jp-tracks-functions' ),
145
			JETPACK__VERSION
146
		);
147
148
		wp_localize_script(
149
			'jetpack-search-widget-admin', 'jetpack_search_filter_admin', array(
150
				'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
151
				'tracksUserData'     => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
152
				'tracksEventData'    => array(
153
					'is_customizer' => (int) is_customize_preview(),
154
				),
155
				'i18n'               => array(
156
					'month'        => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', false ),
157
					'year'         => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', false ),
158
					'monthUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', true ),
159
					'yearUpdated'  => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', true ),
160
				),
161
			)
162
		);
163
164
		wp_enqueue_script( 'jetpack-search-widget-admin' );
165
	}
166
167
	/**
168
	 * Enqueue scripts and styles for the frontend.
169
	 *
170
	 * @since 5.8.0
171
	 */
172
	public function enqueue_frontend_scripts() {
173
		if ( ! is_active_widget( false, false, $this->id_base, true ) || Constants::is_true( 'JETPACK_SEARCH_PROTOTYPE' ) ) {
174
			return;
175
		}
176
177
		wp_enqueue_script(
178
			'jetpack-search-widget',
179
			plugins_url( 'search/js/search-widget.js', __FILE__ ),
180
			array( 'jquery' ),
181
			JETPACK__VERSION,
182
			true
183
		);
184
185
		wp_enqueue_style( 'jetpack-search-widget', plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ) );
186
	}
187
188
	/**
189
	 * Get the list of valid sort types/orders.
190
	 *
191
	 * @since 5.8.0
192
	 *
193
	 * @return array The sort orders.
194
	 */
195
	private function get_sort_types() {
196
		return array(
197
			'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
198
			'date|DESC'      => esc_html__( 'Newest first', 'jetpack' ),
199
			'date|ASC'       => esc_html__( 'Oldest first', 'jetpack' ),
200
		);
201
	}
202
203
	/**
204
	 * Callback for an array_filter() call in order to only get filters for the current widget.
205
	 *
206
	 * @see   Jetpack_Search_Widget::widget()
207
	 *
208
	 * @since 5.7.0
209
	 *
210
	 * @param array $item Filter item.
211
	 *
212
	 * @return bool Whether the current filter item is for the current widget.
213
	 */
214
	function is_for_current_widget( $item ) {
215
		return isset( $item['widget_id'] ) && $this->id == $item['widget_id'];
216
	}
217
218
	/**
219
	 * This method returns a boolean for whether the widget should show site-wide filters for the site.
220
	 *
221
	 * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
222
	 * configured filters via `Jetpack_Search::set_filters()`.
223
	 *
224
	 * @since 5.7.0
225
	 *
226
	 * @return bool Whether the widget should display site-wide filters or not.
227
	 */
228
	public function should_display_sitewide_filters() {
229
		$filter_widgets = get_option( 'widget_jetpack-search-filters' );
230
231
		// This shouldn't be empty, but just for sanity
232
		if ( empty( $filter_widgets ) ) {
233
			return false;
234
		}
235
236
		// If any widget has any filters, return false
237
		foreach ( $filter_widgets as $number => $widget ) {
238
			$widget_id = sprintf( '%s-%d', $this->id_base, $number );
239
			if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
240
				return false;
241
			}
242
		}
243
244
		return true;
245
	}
246
247
	public function jetpack_search_populate_defaults( $instance ) {
248
		$instance = wp_parse_args(
249
			(array) $instance, array(
250
				'title'              => '',
251
				'search_box_enabled' => true,
252
				'user_sort_enabled'  => true,
253
				'sort'               => self::DEFAULT_SORT,
254
				'filters'            => array( array() ),
255
				'post_types'         => array(),
256
			)
257
		);
258
259
		return $instance;
260
	}
261
262
	/**
263
	 * Responsible for rendering the widget on the frontend.
264
	 *
265
	 * @since 5.0.0
266
	 *
267
	 * @param array $args     Widgets args supplied by the theme.
268
	 * @param array $instance The current widget instance.
269
	 */
270
	public function widget( $args, $instance ) {
271
		$instance = $this->jetpack_search_populate_defaults( $instance );
272
273
		$display_filters = false;
274
275
		if ( ( new Status() )->is_development_mode() ) {
276
			echo $args['before_widget'];
277
			?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
278
				<div class="jetpack-search-sort-wrapper">
279
					<label>
280
						<?php esc_html_e( 'Jetpack Search not supported in Development Mode', 'jetpack' ); ?>
281
					</label>
282
				</div>
283
			</div><?php
284
			echo $args['after_widget'];
285
			return;
286
		}
287
288
		if ( is_search() ) {
289
			if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
290
				Jetpack_Search::instance()->update_search_results_aggregations();
291
			}
292
293
			$filters = Jetpack_Search::instance()->get_filters();
294
295
			if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
296
				$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
297
			}
298
299
			if ( ! empty( $filters ) ) {
300
				$display_filters = true;
301
			}
302
		}
303
304
		if ( ! $display_filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
305
			return;
306
		}
307
308
		$title = isset( $instance['title'] ) ? $instance['title'] : '';
309
310
		if ( empty( $title ) ) {
311
			$title = '';
312
		}
313
314
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
315
		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
316
317
		echo $args['before_widget'];
318
		?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="<?php
319
		echo Constants::is_true( 'JETPACK_SEARCH_PROTOTYPE' ) ? 'jetpack-instant-search-wrapper' : '' ?>">
320
		<?php
321
322
		if ( ! empty( $title ) ) {
323
			/**
324
			 * Responsible for displaying the title of the Jetpack Search filters widget.
325
			 *
326
			 * @module search
327
			 *
328
			 * @since  5.7.0
329
			 *
330
			 * @param string $title                The widget's title
331
			 * @param string $args['before_title'] The HTML tag to display before the title
332
			 * @param string $args['after_title']  The HTML tag to display after the title
333
			 */
334
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
335
		}
336
337
		$default_sort            = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
338
		list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
339
		$current_sort            = "{$orderby}|{$order}";
340
341
		// we need to dynamically inject the sort field into the search box when the search box is enabled, and display
342
		// it separately when it's not.
343
		if ( ! empty( $instance['search_box_enabled'] ) ) {
344
			Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
345
		}
346
347
		if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
348
				?>
349
					<div class="jetpack-search-sort-wrapper">
350
				<label>
351
					<?php esc_html_e( 'Sort by', 'jetpack' ); ?>
352
					<select class="jetpack-search-sort">
353 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
354
							<option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
355
								<?php echo esc_html( $label ); ?>
356
							</option>
357
						<?php } ?>
358
					</select>
359
				</label>
360
			</div>
361
		<?php
362
		endif;
363
364
		if ( $display_filters ) {
365
			/**
366
			 * Responsible for rendering filters to narrow down search results.
367
			 *
368
			 * @module search
369
			 *
370
			 * @since  5.8.0
371
			 *
372
			 * @param array $filters    The possible filters for the current query.
373
			 * @param array $post_types An array of post types to limit filtering to.
374
			 */
375
			do_action(
376
				'jetpack_search_render_filters',
377
				$filters,
0 ignored issues
show
Bug introduced by
The variable $filters does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
378
				isset( $instance['post_types'] ) ? $instance['post_types'] : null
379
			);
380
		}
381
382
		$this->maybe_render_sort_javascript( $instance, $order, $orderby );
383
384
		echo '</div>';
385
		echo $args['after_widget'];
386
	}
387
388
	/**
389
	 * Renders JavaScript for the sorting controls on the frontend.
390
	 *
391
	 * This JS is a bit complicated, but here's what it's trying to do:
392
	 * - find the search form
393
	 * - find the orderby/order fields and set default values
394
	 * - detect changes to the sort field, if it exists, and use it to set the order field values
395
	 *
396
	 * @since 5.8.0
397
	 *
398
	 * @param array  $instance The current widget instance.
399
	 * @param string $order    The order to initialize the select with.
400
	 * @param string $orderby  The orderby to initialize the select with.
401
	 */
402
	private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
403
		if ( Constants::is_true( 'JETPACK_SEARCH_PROTOTYPE' ) ) {
404
			return;
405
		}
406
407
		if ( ! empty( $instance['user_sort_enabled'] ) ) :
408
		?>
409
		<script type="text/javascript">
410
				jQuery( document ).ready( function( $ ) {
411
					var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
412
						orderDefault   = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
413
						widgetId       = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
414
						searchQuery    = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
415
						isSearch       = <?php echo (int) is_search(); ?>;
416
417
					var container = $( '#' + widgetId + '-wrapper' ),
418
						form = container.find('.jetpack-search-form form'),
419
						orderBy = form.find( 'input[name=orderby]'),
420
						order = form.find( 'input[name=order]'),
421
						searchInput = form.find( 'input[name="s"]' );
422
423
					orderBy.val( orderByDefault );
424
					order.val( orderDefault );
425
426
					// Some themes don't set the search query, which results in the query being lost
427
					// when doing a sort selection. So, if the query isn't set, let's set it now. This approach
428
					// is chosen over running a regex over HTML for every search query performed.
429
					if ( isSearch && ! searchInput.val() ) {
430
						searchInput.val( searchQuery );
431
					}
432
433
					searchInput.addClass( 'show-placeholder' );
434
435
					container.find( '.jetpack-search-sort' ).change( function( event ) {
436
						var values  = event.target.value.split( '|' );
437
						orderBy.val( values[0] );
438
						order.val( values[1] );
439
440
						form.submit();
441
					});
442
				} );
443
			</script>
444
		<?php
445
		endif;
446
	}
447
448
	/**
449
	 * Convert a sort string into the separate order by and order parts.
450
	 *
451
	 * @since 5.8.0
452
	 *
453
	 * @param string $sort A sort string.
454
	 *
455
	 * @return array Order by and order.
456
	 */
457
	private function sorting_to_wp_query_param( $sort ) {
458
		$parts   = explode( '|', $sort );
459
		$orderby = isset( $_GET['orderby'] )
460
			? $_GET['orderby']
461
			: $parts[0];
462
463
		$order = isset( $_GET['order'] )
464
			? strtoupper( $_GET['order'] )
465
			: ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
466
467
		return array( $orderby, $order );
468
	}
469
470
	/**
471
	 * Updates a particular instance of the widget. Validates and sanitizes the options.
472
	 *
473
	 * @since 5.0.0
474
	 *
475
	 * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
476
	 * @param array $old_instance Old settings for this instance.
477
	 *
478
	 * @return array Settings to save.
479
	 */
480
	public function update( $new_instance, $old_instance ) {
481
		$instance = array();
482
483
		$instance['title']              = sanitize_text_field( $new_instance['title'] );
484
		$instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
485
		$instance['user_sort_enabled']  = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
486
		$instance['sort']               = $new_instance['sort'];
487
		$instance['post_types']         = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
488
			? array()
489
			: array_map( 'sanitize_key', $new_instance['post_types'] );
490
491
		$filters = array();
492
		if ( isset( $new_instance['filter_type'] ) ) {
493
			foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
494
				$count = intval( $new_instance['num_filters'][ $index ] );
495
				$count = min( 50, $count ); // Set max boundary at 50.
496
				$count = max( 1, $count );  // Set min boundary at 1.
497
498
				switch ( $type ) {
499
					case 'taxonomy':
500
						$filters[] = array(
501
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
502
							'type'     => 'taxonomy',
503
							'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
504
							'count'    => $count,
505
						);
506
						break;
507
					case 'post_type':
508
						$filters[] = array(
509
							'name'  => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
510
							'type'  => 'post_type',
511
							'count' => $count,
512
						);
513
						break;
514
					case 'date_histogram':
515
						$filters[] = array(
516
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
517
							'type'     => 'date_histogram',
518
							'count'    => $count,
519
							'field'    => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
520
							'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
521
						);
522
						break;
523
				}
524
			}
525
		}
526
527
		if ( ! empty( $filters ) ) {
528
			$instance['filters'] = $filters;
529
		}
530
531
		return $instance;
532
	}
533
534
	/**
535
	 * Outputs the settings update form.
536
	 *
537
	 * @since 5.0.0
538
	 *
539
	 * @param array $instance Current settings.
540
	 */
541
	public function form( $instance ) {
542
		$instance = $this->jetpack_search_populate_defaults( $instance );
543
544
		$title = strip_tags( $instance['title'] );
545
546
		$hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
547
548
		$classes = sprintf(
549
			'jetpack-search-filters-widget %s %s %s',
550
			$hide_filters ? 'hide-filters' : '',
551
			$instance['search_box_enabled'] ? '' : 'hide-post-types',
552
			$this->id
553
		);
554
		?>
555
		<div class="<?php echo esc_attr( $classes ); ?>">
556
			<p>
557
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
558
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
559
				</label>
560
				<input
561
					class="widefat"
562
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
563
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
564
					type="text"
565
					value="<?php echo esc_attr( $title ); ?>"
566
				/>
567
			</p>
568
569
			<p>
570
				<label>
571
					<input
572
						type="checkbox"
573
						class="jetpack-search-filters-widget__search-box-enabled"
574
						name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
575
						<?php checked( $instance['search_box_enabled'] ); ?>
576
					/>
577
					<?php esc_html_e( 'Show search box', 'jetpack' ); ?>
578
				</label>
579
			</p>
580
			<p>
581
				<label>
582
					<input
583
						type="checkbox"
584
						class="jetpack-search-filters-widget__sort-controls-enabled"
585
						name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
586
						<?php checked( $instance['user_sort_enabled'] ); ?>
587
						<?php disabled( ! $instance['search_box_enabled'] ); ?>
588
					/>
589
					<?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
590
				</label>
591
			</p>
592
593
			<p class="jetpack-search-filters-widget__post-types-select">
594
				<label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
595
				<?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
596
					<label>
597
						<input
598
							type="checkbox"
599
							value="<?php echo esc_attr( $post_type->name ); ?>"
600
							name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
601
							<?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
602
						/>&nbsp;
603
						<?php echo esc_html( $post_type->label ); ?>
604
					</label>
605
				<?php endforeach; ?>
606
			</p>
607
608
			<p>
609
				<label>
610
					<?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
611
					<select
612
						name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
613
						class="widefat jetpack-search-filters-widget__sort-order">
614 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
615
							<option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
616
								<?php echo esc_html( $label ); ?>
617
							</option>
618
						<?php } ?>
619
					</select>
620
				</label>
621
			</p>
622
623
			<?php if ( ! $hide_filters ) : ?>
624
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
625
					<?php echo $this->render_widget_edit_filter( array(), true ); ?>
626
				</script>
627
				<div class="jetpack-search-filters-widget__filters">
628
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
629
						<?php $this->render_widget_edit_filter( $filter ); ?>
630
					<?php endforeach; ?>
631
				</div>
632
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
633
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
634
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
635
					</a>
636
				</p>
637
				<noscript>
638
					<p class="jetpack-search-filters-help">
639
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
640
					</p>
641
				</noscript>
642
				<?php if ( is_customize_preview() ) : ?>
643
					<p class="jetpack-search-filters-help">
644
						<a href="https://jetpack.com/support/search/#filters-not-showing-up" target="_blank">
645
							<?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
646
						</a>
647
					</p>
648
				<?php endif; ?>
649
			<?php endif; ?>
650
		</div>
651
		<?php
652
	}
653
654
	/**
655
	 * We need to render HTML in two formats: an Underscore template (client-side)
656
	 * and native PHP (server-side). This helper function allows for easy rendering
657
	 * of attributes in both formats.
658
	 *
659
	 * @since 5.8.0
660
	 *
661
	 * @param string $name        Attribute name.
662
	 * @param string $value       Attribute value.
663
	 * @param bool   $is_template Whether this is for an Underscore template or not.
664
	 */
665
	private function render_widget_attr( $name, $value, $is_template ) {
666
		echo $is_template ? "<%= $name %>" : esc_attr( $value );
667
	}
668
669
	/**
670
	 * We need to render HTML in two formats: an Underscore template (client-size)
671
	 * and native PHP (server-side). This helper function allows for easy rendering
672
	 * of the "selected" attribute in both formats.
673
	 *
674
	 * @since 5.8.0
675
	 *
676
	 * @param string $name        Attribute name.
677
	 * @param string $value       Attribute value.
678
	 * @param string $compare     Value to compare to the attribute value to decide if it should be selected.
679
	 * @param bool   $is_template Whether this is for an Underscore template or not.
680
	 */
681
	private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
682
		$compare_js = rawurlencode( $compare );
683
		echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
684
	}
685
686
	/**
687
	 * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
688
	 *
689
	 * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
690
	 *
691
	 * @since 5.7.0
692
	 *
693
	 * @param array $filter      The filter to render.
694
	 * @param bool  $is_template Whether this is for an Underscore template or not.
695
	 */
696
	public function render_widget_edit_filter( $filter, $is_template = false ) {
697
		$args = wp_parse_args(
698
			$filter, array(
699
				'name'      => '',
700
				'type'      => 'taxonomy',
701
				'taxonomy'  => '',
702
				'post_type' => '',
703
				'field'     => '',
704
				'interval'  => '',
705
				'count'     => self::DEFAULT_FILTER_COUNT,
706
			)
707
		);
708
709
		$args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
710
711
		?>
712
		<div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
713
			<p class="jetpack-search-filters-widget__type-select">
714
				<label>
715
					<?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
716
					<select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
717
						<option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
718
							<?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
719
						</option>
720
						<option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
721
							<?php esc_html_e( 'Post Type', 'jetpack' ); ?>
722
						</option>
723
						<option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
724
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
725
						</option>
726
					</select>
727
				</label>
728
			</p>
729
730
			<p class="jetpack-search-filters-widget__taxonomy-select">
731
				<label>
732
					<?php
733
						esc_html_e( 'Choose a taxonomy:', 'jetpack' );
734
						$seen_taxonomy_labels = array();
735
					?>
736
					<select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
737
						<?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
738
							<option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
739
								<?php
740
									$label = in_array( $taxonomy->label, $seen_taxonomy_labels )
741
										? sprintf(
742
											/* translators: %1$s is the taxonomy name, %2s is the name of its type to help distinguish between several taxonomies with the same name, e.g. category and tag. */
743
											_x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
744
											$taxonomy->label,
745
											$taxonomy->name
746
										)
747
										: $taxonomy->label;
748
									echo esc_html( $label );
749
									$seen_taxonomy_labels[] = $taxonomy->label;
750
								?>
751
							</option>
752
						<?php endforeach; ?>
753
					</select>
754
				</label>
755
			</p>
756
757
			<p class="jetpack-search-filters-widget__date-histogram-select">
758
				<label>
759
					<?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
760
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
761
						<option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
762
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
763
						</option>
764
						<option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
765
							<?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
766
						</option>
767
						<option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
768
							<?php esc_html_e( 'Modified', 'jetpack' ); ?>
769
						</option>
770
						<option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
771
							<?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
772
						</option>
773
					</select>
774
				</label>
775
			</p>
776
777
			<p class="jetpack-search-filters-widget__date-histogram-select">
778
				<label>
779
					<?php esc_html_e( 'Choose an interval:' ); ?>
780
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
781
						<option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
782
							<?php esc_html_e( 'Month', 'jetpack' ); ?>
783
						</option>
784
						<option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
785
							<?php esc_html_e( 'Year', 'jetpack' ); ?>
786
						</option>
787
					</select>
788
				</label>
789
			</p>
790
791
			<p class="jetpack-search-filters-widget__title">
792
				<label>
793
					<?php esc_html_e( 'Title:', 'jetpack' ); ?>
794
					<input
795
						class="widefat"
796
						type="text"
797
						name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
798
						value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
799
						placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
800
					/>
801
				</label>
802
			</p>
803
804
			<p>
805
				<label>
806
					<?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
807
					<input
808
						class="widefat filter-count"
809
						name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
810
						type="number"
811
						value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
812
						min="1"
813
						max="50"
814
						step="1"
815
						required
816
					/>
817
				</label>
818
			</p>
819
820
			<p class="jetpack-search-filters-widget__controls">
821
				<a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
822
			</p>
823
		</div>
824
	<?php
825
	}
826
}
827