Completed
Push — add/stats-package ( c3aabb...99d182 )
by
unknown
148:14 queued 140:08
created

Jetpack_Search_Widget::widget_non_instant()   F

Complexity

Conditions 18
Paths 585

Size

Total Lines 98

Duplication

Lines 8
Ratio 8.16 %

Importance

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

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...
356
357
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
358
		?>
359
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" >
360
		<?php
361
362
		if ( ! empty( $title ) ) {
363
			/**
364
			 * Responsible for displaying the title of the Jetpack Search filters widget.
365
			 *
366
			 * @module search
367
			 *
368
			 * @since  5.7.0
369
			 *
370
			 * @param string $title                The widget's title
371
			 * @param string $args['before_title'] The HTML tag to display before the title
372
			 * @param string $args['after_title']  The HTML tag to display after the title
373
			 */
374
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
375
		}
376
377
		$default_sort            = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
378
		list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
379
		$current_sort            = "{$orderby}|{$order}";
380
381
		// we need to dynamically inject the sort field into the search box when the search box is enabled, and display
382
		// it separately when it's not.
383
		if ( ! empty( $instance['search_box_enabled'] ) ) {
384
			Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
385
		}
386
387
		if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
388
				?>
389
					<div class="jetpack-search-sort-wrapper">
390
				<label>
391
					<?php esc_html_e( 'Sort by', 'jetpack' ); ?>
392
					<select class="jetpack-search-sort">
393 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
394
							<option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
395
								<?php echo esc_html( $label ); ?>
396
							</option>
397
						<?php } ?>
398
					</select>
399
				</label>
400
			</div>
401
		<?php
402
		endif;
403
404
		if ( $display_filters ) {
405
			/**
406
			 * Responsible for rendering filters to narrow down search results.
407
			 *
408
			 * @module search
409
			 *
410
			 * @since  5.8.0
411
			 *
412
			 * @param array $filters    The possible filters for the current query.
413
			 * @param array $post_types An array of post types to limit filtering to.
414
			 */
415
			do_action(
416
				'jetpack_search_render_filters',
417
				$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...
418
				isset( $instance['post_types'] ) ? $instance['post_types'] : null
419
			);
420
		}
421
422
		$this->maybe_render_sort_javascript( $instance, $order, $orderby );
423
424
		echo '</div>';
425
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
426
	}
427
428
	/**
429
	 * Render the instant frontend widget.
430
	 *
431
	 * @since 8.3.0
432
	 *
433
	 * @param array $args     Widgets args supplied by the theme.
434
	 * @param array $instance The current widget instance.
435
	 */
436
	public function widget_instant( $args, $instance ) {
437
		if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
438
			Jetpack_Search::instance()->update_search_results_aggregations();
439
		}
440
441
		$filters = Jetpack_Search::instance()->get_filters();
442 View Code Duplication
		if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
443
			$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
444
		}
445
446
		$display_filters = ! empty( $filters );
447
448
		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
449
450
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
451
		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $instance.

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...
452
453
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
454
		?>
455
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
456
		<?php
457
458
		if ( ! empty( $title ) ) {
459
			/**
460
			 * Responsible for displaying the title of the Jetpack Search filters widget.
461
			 *
462
			 * @module search
463
			 *
464
			 * @since  5.7.0
465
			 *
466
			 * @param string $title                The widget's title
467
			 * @param string $args['before_title'] The HTML tag to display before the title
468
			 * @param string $args['after_title']  The HTML tag to display after the title
469
			 */
470
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
471
		}
472
473
		Jetpack_Search_Template_Tags::render_widget_search_form( array(), '', '' );
474
475
		if ( $display_filters ) {
476
			/**
477
			 * Responsible for rendering filters to narrow down search results.
478
			 *
479
			 * @module search
480
			 *
481
			 * @since  5.8.0
482
			 *
483
			 * @param array $filters    The possible filters for the current query.
484
			 * @param array $post_types An array of post types to limit filtering to.
485
			 */
486
			do_action(
487
				'jetpack_search_render_filters',
488
				$filters,
489
				null
490
			);
491
		}
492
493
		echo '</div>';
494
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
495
	}
496
497
	/**
498
	 * Render the instant widget for the overlay.
499
	 *
500
	 * @since 8.3.0
501
	 *
502
	 * @param array $args     Widgets args supplied by the theme.
503
	 * @param array $instance The current widget instance.
504
	 */
505
	public function widget_empty_instant( $args, $instance ) {
506
		$title = isset( $instance['title'] ) ? $instance['title'] : '';
507
508
		if ( empty( $title ) ) {
509
			$title = '';
510
		}
511
512
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
513
		$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $instance.

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...
514
515
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
516
		?>
517
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
518
		<?php
519
520
		if ( ! empty( $title ) ) {
521
			/**
522
			 * Responsible for displaying the title of the Jetpack Search filters widget.
523
			 *
524
			 * @module search
525
			 *
526
			 * @since  5.7.0
527
			 *
528
			 * @param string $title                The widget's title
529
			 * @param string $args['before_title'] The HTML tag to display before the title
530
			 * @param string $args['after_title']  The HTML tag to display after the title
531
			 */
532
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
533
		}
534
535
		echo '</div>';
536
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
537
	}
538
539
540
	/**
541
	 * Renders JavaScript for the sorting controls on the frontend.
542
	 *
543
	 * This JS is a bit complicated, but here's what it's trying to do:
544
	 * - find the search form
545
	 * - find the orderby/order fields and set default values
546
	 * - detect changes to the sort field, if it exists, and use it to set the order field values
547
	 *
548
	 * @since 5.8.0
549
	 *
550
	 * @param array  $instance The current widget instance.
551
	 * @param string $order    The order to initialize the select with.
552
	 * @param string $orderby  The orderby to initialize the select with.
553
	 */
554
	private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
555
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
556
			return;
557
		}
558
559
		if ( ! empty( $instance['user_sort_enabled'] ) ) :
560
		?>
561
		<script type="text/javascript">
562
			var jetpackSearchModuleSorting = function() {
563
				var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
564
					orderDefault   = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
565
					widgetId       = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
566
					searchQuery    = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
567
					isSearch       = <?php echo (int) is_search(); ?>;
568
569
				var container = document.getElementById( widgetId + '-wrapper' ),
570
					form = container.querySelector( '.jetpack-search-form form' ),
571
					orderBy = form.querySelector( 'input[name=orderby]' ),
572
					order = form.querySelector( 'input[name=order]' ),
573
					searchInput = form.querySelector( 'input[name="s"]' ),
574
					sortSelectInput = container.querySelector( '.jetpack-search-sort' );
575
576
				orderBy.value = orderByDefault;
577
				order.value = orderDefault;
578
579
				// Some themes don't set the search query, which results in the query being lost
580
				// when doing a sort selection. So, if the query isn't set, let's set it now. This approach
581
				// is chosen over running a regex over HTML for every search query performed.
582
				if ( isSearch && ! searchInput.value ) {
583
					searchInput.value = searchQuery;
584
				}
585
586
				searchInput.classList.add( 'show-placeholder' );
587
588
				sortSelectInput.addEventListener( 'change', function( event ) {
589
					var values  = event.target.value.split( '|' );
590
					orderBy.value = values[0];
591
					order.value = values[1];
592
593
					form.submit();
594
				} );
595
			}
596
597
			if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
598
				jetpackSearchModuleSorting();
599
			} else {
600
				document.addEventListener( 'DOMContentLoaded', jetpackSearchModuleSorting );
601
			}
602
			</script>
603
		<?php
604
		endif;
605
	}
606
607
	/**
608
	 * Convert a sort string into the separate order by and order parts.
609
	 *
610
	 * @since 5.8.0
611
	 *
612
	 * @param string $sort A sort string.
613
	 *
614
	 * @return array Order by and order.
615
	 */
616
	private function sorting_to_wp_query_param( $sort ) {
617
		$parts   = explode( '|', $sort );
618
		$orderby = isset( $_GET['orderby'] )
619
			? $_GET['orderby']
620
			: $parts[0];
621
622
		$order = isset( $_GET['order'] )
623
			? strtoupper( $_GET['order'] )
624
			: ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
625
626
		return array( $orderby, $order );
627
	}
628
629
	/**
630
	 * Updates a particular instance of the widget. Validates and sanitizes the options.
631
	 *
632
	 * @since 5.0.0
633
	 *
634
	 * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
635
	 * @param array $old_instance Old settings for this instance.
636
	 *
637
	 * @return array Settings to save.
638
	 */
639
	public function update( $new_instance, $old_instance ) {
640
		$instance = array();
641
642
		$instance['title']              = sanitize_text_field( $new_instance['title'] );
643
		$instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
644
		$instance['user_sort_enabled']  = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
645
		$instance['sort']               = $new_instance['sort'];
646
		$instance['post_types']         = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
647
			? array()
648
			: array_map( 'sanitize_key', $new_instance['post_types'] );
649
650
		$filters = array();
651
		if ( isset( $new_instance['filter_type'] ) ) {
652
			foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
653
				$count = intval( $new_instance['num_filters'][ $index ] );
654
				$count = min( 50, $count ); // Set max boundary at 50.
655
				$count = max( 1, $count );  // Set min boundary at 1.
656
657
				switch ( $type ) {
658
					case 'taxonomy':
659
						$filters[] = array(
660
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
661
							'type'     => 'taxonomy',
662
							'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
663
							'count'    => $count,
664
						);
665
						break;
666
					case 'post_type':
667
						$filters[] = array(
668
							'name'  => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
669
							'type'  => 'post_type',
670
							'count' => $count,
671
						);
672
						break;
673
					case 'date_histogram':
674
						$filters[] = array(
675
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
676
							'type'     => 'date_histogram',
677
							'count'    => $count,
678
							'field'    => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
679
							'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
680
						);
681
						break;
682
				}
683
			}
684
		}
685
686
		if ( ! empty( $filters ) ) {
687
			$instance['filters'] = $filters;
688
		}
689
690
		return $instance;
691
	}
692
693
	/**
694
	 * Outputs the settings update form.
695
	 *
696
	 * @since 5.0.0
697
	 *
698
	 * @param array $instance Previously saved values from database.
699
	 */
700
	public function form( $instance ) {
701
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
702
			return $this->form_for_instant_search( $instance );
703
		}
704
705
		$instance = $this->jetpack_search_populate_defaults( $instance );
706
707
		$title = strip_tags( $instance['title'] );
708
709
		$hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
710
711
		$classes = sprintf(
712
			'jetpack-search-filters-widget %s %s %s',
713
			$hide_filters ? 'hide-filters' : '',
714
			$instance['search_box_enabled'] ? '' : 'hide-post-types',
715
			$this->id
716
		);
717
		?>
718
		<div class="<?php echo esc_attr( $classes ); ?>">
719
			<p>
720
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
721
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
722
				</label>
723
				<input
724
					class="widefat"
725
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
726
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
727
					type="text"
728
					value="<?php echo esc_attr( $title ); ?>"
729
				/>
730
			</p>
731
732
			<p>
733
				<label>
734
					<input
735
						type="checkbox"
736
						class="jetpack-search-filters-widget__search-box-enabled"
737
						name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
738
						<?php checked( $instance['search_box_enabled'] ); ?>
739
					/>
740
					<?php esc_html_e( 'Show search box', 'jetpack' ); ?>
741
				</label>
742
			</p>
743
744
			<p>
745
				<label>
746
					<input
747
						type="checkbox"
748
						class="jetpack-search-filters-widget__sort-controls-enabled"
749
						name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
750
						<?php checked( $instance['user_sort_enabled'] ); ?>
751
						<?php disabled( ! $instance['search_box_enabled'] ); ?>
752
					/>
753
					<?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
754
				</label>
755
			</p>
756
757
			<p class="jetpack-search-filters-widget__post-types-select">
758
				<label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
759 View Code Duplication
				<?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
760
					<label>
761
						<input
762
							type="checkbox"
763
							value="<?php echo esc_attr( $post_type->name ); ?>"
764
							name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
765
							<?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
766
						/>&nbsp;
767
						<?php echo esc_html( $post_type->label ); ?>
768
					</label>
769
				<?php endforeach; ?>
770
			</p>
771
772
			<p>
773
				<label>
774
					<?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
775
					<select
776
						name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
777
						class="widefat jetpack-search-filters-widget__sort-order">
778 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
779
							<option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
780
								<?php echo esc_html( $label ); ?>
781
							</option>
782
						<?php } ?>
783
					</select>
784
				</label>
785
			</p>
786
787
			<?php if ( ! $hide_filters ) : ?>
788
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
789
					<?php echo $this->render_widget_edit_filter( array(), true ); ?>
790
				</script>
791
				<div class="jetpack-search-filters-widget__filters">
792
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
793
						<?php $this->render_widget_edit_filter( $filter ); ?>
794
					<?php endforeach; ?>
795
				</div>
796
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
797
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
798
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
799
					</a>
800
				</p>
801
				<noscript>
802
					<p class="jetpack-search-filters-help">
803
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
804
					</p>
805
				</noscript>
806
				<?php if ( is_customize_preview() ) : ?>
807
					<p class="jetpack-search-filters-help">
808
						<a href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-search', array( 'anchor' => 'filters-not-showing-up' ) ) ); ?>" target="_blank">
809
							<?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
810
						</a>
811
					</p>
812
				<?php endif; ?>
813
			<?php endif; ?>
814
		</div>
815
		<?php
816
	}
817
818
	/**
819
	 * Outputs the widget update form to be used in the Customizer for Instant Search.
820
	 *
821
	 * @since 8.6.0
822
	 *
823
	 * @param array $instance Previously saved values from database.
824
	 */
825
	private function form_for_instant_search( $instance ) {
826
		$instance = $this->populate_defaults_for_instant_search( $instance );
827
		$classes  = sprintf( 'jetpack-search-filters-widget %s', $this->id );
828
829
		?>
830
		<div class="<?php echo esc_attr( $classes ); ?>">
831
			<!-- Title control -->
832
			<p>
833
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
834
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
835
				</label>
836
				<input
837
					class="widefat"
838
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
839
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
840
					type="text"
841
					value="<?php echo esc_attr( wp_strip_all_tags( $instance['title'] ) ); ?>"
842
				/>
843
			</p>
844
845
			<!-- Post types control -->
846
			<p class="jetpack-search-filters-widget__post-types-select">
847
				<label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
848 View Code Duplication
				<?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
849
					<label>
850
						<input
851
							type="checkbox"
852
							value="<?php echo esc_attr( $post_type->name ); ?>"
853
							name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
854
							<?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'], true ) ); ?>
855
						/>&nbsp;
856
						<?php echo esc_html( $post_type->label ); ?>
857
					</label>
858
				<?php endforeach; ?>
859
			</p>
860
861
			<!-- Filters control -->
862
			<?php if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() ) : ?>
863
				<div class="jetpack-search-filters-widget__filters">
864
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
865
						<?php $this->render_widget_edit_filter( $filter ); ?>
866
					<?php endforeach; ?>
867
				</div>
868
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
869
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
870
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
871
					</a>
872
				</p>
873
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
874
					<?php $this->render_widget_edit_filter( array(), true ); ?>
875
				</script>
876
				<noscript>
877
					<p class="jetpack-search-filters-help">
878
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
879
					</p>
880
				</noscript>
881
			<?php endif; ?>
882
		</div>
883
		<?php
884
	}
885
886
	/**
887
	 * We need to render HTML in two formats: an Underscore template (client-side)
888
	 * and native PHP (server-side). This helper function allows for easy rendering
889
	 * of attributes in both formats.
890
	 *
891
	 * @since 5.8.0
892
	 *
893
	 * @param string $name        Attribute name.
894
	 * @param string $value       Attribute value.
895
	 * @param bool   $is_template Whether this is for an Underscore template or not.
896
	 */
897
	private function render_widget_attr( $name, $value, $is_template ) {
898
		echo $is_template ? "<%= $name %>" : esc_attr( $value );
899
	}
900
901
	/**
902
	 * We need to render HTML in two formats: an Underscore template (client-size)
903
	 * and native PHP (server-side). This helper function allows for easy rendering
904
	 * of the "selected" attribute in both formats.
905
	 *
906
	 * @since 5.8.0
907
	 *
908
	 * @param string $name        Attribute name.
909
	 * @param string $value       Attribute value.
910
	 * @param string $compare     Value to compare to the attribute value to decide if it should be selected.
911
	 * @param bool   $is_template Whether this is for an Underscore template or not.
912
	 */
913
	private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
914
		$compare_js = rawurlencode( $compare );
915
		echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
916
	}
917
918
	/**
919
	 * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
920
	 *
921
	 * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
922
	 *
923
	 * @since 5.7.0
924
	 *
925
	 * @param array $filter      The filter to render.
926
	 * @param bool  $is_template Whether this is for an Underscore template or not.
927
	 */
928
	public function render_widget_edit_filter( $filter, $is_template = false ) {
929
		$args = wp_parse_args(
930
			$filter, array(
0 ignored issues
show
Documentation introduced by
array('name' => '', 'typ...::DEFAULT_FILTER_COUNT) is of type array<string,string|inte...ng","count":"integer"}>, 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...
931
				'name'      => '',
932
				'type'      => 'taxonomy',
933
				'taxonomy'  => '',
934
				'post_type' => '',
935
				'field'     => '',
936
				'interval'  => '',
937
				'count'     => self::DEFAULT_FILTER_COUNT,
938
			)
939
		);
940
941
		$args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
942
943
		?>
944
		<div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
945
			<p class="jetpack-search-filters-widget__type-select">
946
				<label>
947
					<?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
948
					<select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
949
						<option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
950
							<?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
951
						</option>
952
						<option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
953
							<?php esc_html_e( 'Post Type', 'jetpack' ); ?>
954
						</option>
955
						<option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
956
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
957
						</option>
958
					</select>
959
				</label>
960
			</p>
961
962
			<p class="jetpack-search-filters-widget__taxonomy-select">
963
				<label>
964
					<?php
965
						esc_html_e( 'Choose a taxonomy:', 'jetpack' );
966
						$seen_taxonomy_labels = array();
967
					?>
968
					<select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
969
						<?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
970
							<option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
971
								<?php
972
									$label = in_array( $taxonomy->label, $seen_taxonomy_labels )
973
										? sprintf(
974
											/* 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. */
975
											_x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
976
											$taxonomy->label,
977
											$taxonomy->name
978
										)
979
										: $taxonomy->label;
980
									echo esc_html( $label );
981
									$seen_taxonomy_labels[] = $taxonomy->label;
982
								?>
983
							</option>
984
						<?php endforeach; ?>
985
					</select>
986
				</label>
987
			</p>
988
989
			<p class="jetpack-search-filters-widget__date-histogram-select">
990
				<label>
991
					<?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
992
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
993
						<option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
994
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
995
						</option>
996
						<option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
997
							<?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
998
						</option>
999
						<option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
1000
							<?php esc_html_e( 'Modified', 'jetpack' ); ?>
1001
						</option>
1002
						<option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
1003
							<?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
1004
						</option>
1005
					</select>
1006
				</label>
1007
			</p>
1008
1009
			<p class="jetpack-search-filters-widget__date-histogram-select">
1010
				<label>
1011
					<?php esc_html_e( 'Choose an interval:', 'jetpack' ); ?>
1012
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
1013
						<option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
1014
							<?php esc_html_e( 'Month', 'jetpack' ); ?>
1015
						</option>
1016
						<option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
1017
							<?php esc_html_e( 'Year', 'jetpack' ); ?>
1018
						</option>
1019
					</select>
1020
				</label>
1021
			</p>
1022
1023
			<p class="jetpack-search-filters-widget__title">
1024
				<label>
1025
					<?php esc_html_e( 'Title:', 'jetpack' ); ?>
1026
					<input
1027
						class="widefat"
1028
						type="text"
1029
						name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
1030
						value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
1031
						placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
1032
					/>
1033
				</label>
1034
			</p>
1035
1036
			<p>
1037
				<label>
1038
					<?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
1039
					<input
1040
						class="widefat filter-count"
1041
						name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
1042
						type="number"
1043
						value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
1044
						min="1"
1045
						max="50"
1046
						step="1"
1047
						required
1048
					/>
1049
				</label>
1050
			</p>
1051
1052
			<p class="jetpack-search-filters-widget__controls">
1053
				<a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
1054
			</p>
1055
		</div>
1056
	<?php
1057
	}
1058
}
1059