Completed
Push — add/admin-page-package ( 7976aa...cbfc2c )
by
unknown
136:41 queued 124:01
created

Jetpack_Search_Widget   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 991
Duplicated Lines 1.61 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
dl 16
loc 991
rs 1.609
c 0
b 0
f 0
wmc 97
lcom 1
cbo 9

22 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 34 6
A is_search_active() 0 3 1
A activate_search() 0 3 1
A widget_admin_setup() 0 31 1
A enqueue_frontend_scripts() 0 15 3
A get_sort_types() 0 7 2
A is_for_current_widget() 0 3 2
A should_display_sitewide_filters() 0 18 5
A jetpack_search_populate_defaults() 0 14 1
A populate_defaults_for_instant_search() 0 9 1
A widget() 0 26 4
F widget_non_instant() 8 98 18
B widget_instant() 3 60 7
A widget_empty_instant() 0 33 4
B maybe_render_sort_javascript() 0 52 5
A sorting_to_wp_query_param() 0 12 5
C update() 0 53 11
C form() 5 117 10
A form_for_instant_search() 0 44 3
A render_widget_attr() 0 3 2
A render_widget_option_selected() 0 4 2
B render_widget_edit_filter() 0 130 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Search_Widget often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Search_Widget, and based on these observations, apply Extract Interface, too.

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
use Automattic\Jetpack\Tracking;
14
15
add_action( 'widgets_init', 'jetpack_search_widget_init' );
16
17
function jetpack_search_widget_init() {
18
	if (
19
		! Jetpack::is_connection_ready()
20
		|| ( method_exists( 'Jetpack_Plan', 'supports' ) && ! Jetpack_Plan::supports( 'search' ) )
21
	) {
22
		return;
23
	}
24
25
	require_once JETPACK__PLUGIN_DIR . 'modules/search/class.jetpack-search-helpers.php';
26
	require_once JETPACK__PLUGIN_DIR . 'modules/search/class-jetpack-search-options.php';
27
28
	register_widget( 'Jetpack_Search_Widget' );
29
}
30
31
/**
32
 * Provides a widget to show available/selected filters on searches.
33
 *
34
 * @since 5.0.0
35
 *
36
 * @see   WP_Widget
37
 */
38
class Jetpack_Search_Widget extends WP_Widget {
39
40
	/**
41
	 * The Jetpack_Search instance.
42
	 *
43
	 * @since 5.7.0
44
	 * @var Jetpack_Search
45
	 */
46
	protected $jetpack_search;
47
48
	/**
49
	 * Number of aggregations (filters) to show by default.
50
	 *
51
	 * @since 5.8.0
52
	 * @var int
53
	 */
54
	const DEFAULT_FILTER_COUNT = 5;
55
56
	/**
57
	 * Default sort order for search results.
58
	 *
59
	 * @since 5.8.0
60
	 * @var string
61
	 */
62
	const DEFAULT_SORT = 'relevance_desc';
63
64
	/**
65
	 * Jetpack_Search_Widget constructor.
66
	 *
67
	 * @since 5.0.0
68
	 */
69
	public function __construct( $name = null ) {
70
		if ( empty( $name ) ) {
71
			$name = esc_html__( 'Search', 'jetpack' );
72
		}
73
		parent::__construct(
74
			Jetpack_Search_Helpers::FILTER_WIDGET_BASE,
75
			/** This filter is documented in modules/widgets/facebook-likebox.php */
76
			apply_filters( 'jetpack_widget_name', $name ),
77
			array(
78
				'classname'   => 'jetpack-filters widget_search',
79
				'description' => __( 'Instant search and filtering to help visitors quickly find relevant answers and explore your site.', 'jetpack' ),
80
			)
81
		);
82
83
		if (
84
			Jetpack_Search_Helpers::is_active_widget( $this->id ) &&
85
			! $this->is_search_active()
86
		) {
87
			$this->activate_search();
88
		}
89
90
		if ( is_admin() ) {
91
			add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
92
		} else {
93
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
94
		}
95
96
		add_action( 'jetpack_search_render_filters_widget_title', array( 'Jetpack_Search_Template_Tags', 'render_widget_title' ), 10, 3 );
97
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
98
			add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_instant_filters' ), 10, 2 );
99
		} else {
100
			add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_available_filters' ), 10, 2 );
101
		}
102
	}
103
104
	/**
105
	 * Check whether search is currently active
106
	 *
107
	 * @since 6.3
108
	 */
109
	public function is_search_active() {
110
		return Jetpack::is_module_active( 'search' );
111
	}
112
113
	/**
114
	 * Activate search
115
	 *
116
	 * @since 6.3
117
	 */
118
	public function activate_search() {
119
		Jetpack::activate_module( 'search', false, false );
120
	}
121
122
123
	/**
124
	 * Enqueues the scripts and styles needed for the customizer.
125
	 *
126
	 * @since 5.7.0
127
	 */
128
	public function widget_admin_setup() {
129
		wp_enqueue_style( 'widget-jetpack-search-filters', plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ) );
130
131
		// Register jp-tracks and jp-tracks-functions.
132
		Tracking::register_tracks_functions_scripts();
133
134
		wp_register_script(
135
			'jetpack-search-widget-admin',
136
			plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
137
			array( 'jquery', 'jquery-ui-sortable', 'jp-tracks-functions' ),
138
			JETPACK__VERSION
139
		);
140
141
		wp_localize_script(
142
			'jetpack-search-widget-admin', 'jetpack_search_filter_admin', array(
143
				'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
144
				'tracksUserData'     => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
145
				'tracksEventData'    => array(
146
					'is_customizer' => (int) is_customize_preview(),
147
				),
148
				'i18n'               => array(
149
					'month'        => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', false ),
150
					'year'         => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', false ),
151
					'monthUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', true ),
152
					'yearUpdated'  => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', true ),
153
				),
154
			)
155
		);
156
157
		wp_enqueue_script( 'jetpack-search-widget-admin' );
158
	}
159
160
	/**
161
	 * Enqueue scripts and styles for the frontend.
162
	 *
163
	 * @since 5.8.0
164
	 */
165
	public function enqueue_frontend_scripts() {
166
		if ( ! is_active_widget( false, false, $this->id_base, true ) || Jetpack_Search_Options::is_instant_enabled() ) {
167
			return;
168
		}
169
170
		wp_enqueue_script(
171
			'jetpack-search-widget',
172
			plugins_url( 'search/js/search-widget.js', __FILE__ ),
173
			array(),
174
			JETPACK__VERSION,
175
			true
176
		);
177
178
		wp_enqueue_style( 'jetpack-search-widget', plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ) );
179
	}
180
181
	/**
182
	 * Get the list of valid sort types/orders.
183
	 *
184
	 * @since 5.8.0
185
	 *
186
	 * @return array The sort orders.
187
	 */
188
	private function get_sort_types() {
189
		return array(
190
			'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
191
			'date|DESC'      => esc_html__( 'Newest first', 'jetpack' ),
192
			'date|ASC'       => esc_html__( 'Oldest first', 'jetpack' ),
193
		);
194
	}
195
196
	/**
197
	 * Callback for an array_filter() call in order to only get filters for the current widget.
198
	 *
199
	 * @see   Jetpack_Search_Widget::widget()
200
	 *
201
	 * @since 5.7.0
202
	 *
203
	 * @param array $item Filter item.
204
	 *
205
	 * @return bool Whether the current filter item is for the current widget.
206
	 */
207
	function is_for_current_widget( $item ) {
208
		return isset( $item['widget_id'] ) && $this->id == $item['widget_id'];
209
	}
210
211
	/**
212
	 * This method returns a boolean for whether the widget should show site-wide filters for the site.
213
	 *
214
	 * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
215
	 * configured filters via `Jetpack_Search::set_filters()`.
216
	 *
217
	 * @since 5.7.0
218
	 *
219
	 * @return bool Whether the widget should display site-wide filters or not.
220
	 */
221
	public function should_display_sitewide_filters() {
222
		$filter_widgets = get_option( 'widget_jetpack-search-filters' );
223
224
		// This shouldn't be empty, but just for sanity
225
		if ( empty( $filter_widgets ) ) {
226
			return false;
227
		}
228
229
		// If any widget has any filters, return false
230
		foreach ( $filter_widgets as $number => $widget ) {
231
			$widget_id = sprintf( '%s-%d', $this->id_base, $number );
232
			if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
233
				return false;
234
			}
235
		}
236
237
		return true;
238
	}
239
240
	public function jetpack_search_populate_defaults( $instance ) {
241
		$instance = wp_parse_args(
242
			(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...
243
				'title'              => '',
244
				'search_box_enabled' => true,
245
				'user_sort_enabled'  => true,
246
				'sort'               => self::DEFAULT_SORT,
247
				'filters'            => array( array() ),
248
				'post_types'         => array(),
249
			)
250
		);
251
252
		return $instance;
253
	}
254
255
	/**
256
	 * Populates the instance array with appropriate default values.
257
	 *
258
	 * @since 8.6.0
259
	 * @param array $instance Previously saved values from database.
260
	 * @return array Instance array with default values approprate for instant search
261
	 */
262
	public function populate_defaults_for_instant_search( $instance ) {
263
		return wp_parse_args(
264
			(array) $instance,
265
			array(
0 ignored issues
show
Documentation introduced by
array('title' => '', 'filters' => array()) is of type array<string,string|arra...ng","filters":"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...
266
				'title'   => '',
267
				'filters' => array(),
268
			)
269
		);
270
	}
271
272
	/**
273
	 * Responsible for rendering the widget on the frontend.
274
	 *
275
	 * @since 5.0.0
276
	 *
277
	 * @param array $args     Widgets args supplied by the theme.
278
	 * @param array $instance The current widget instance.
279
	 */
280
	public function widget( $args, $instance ) {
281
		$instance = $this->jetpack_search_populate_defaults( $instance );
282
283
		if ( ( new Status() )->is_offline_mode() ) {
284
			echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
285
			?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
286
				<div class="jetpack-search-sort-wrapper">
287
					<label>
288
						<?php esc_html_e( 'Jetpack Search not supported in Offline Mode', 'jetpack' ); ?>
289
					</label>
290
				</div>
291
			</div><?php
292
			echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
293
			return;
294
		}
295
296
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
297
			if ( 'jetpack-instant-search-sidebar' === $args['id'] ) {
298
				$this->widget_empty_instant( $args, $instance );
299
			} else {
300
				$this->widget_instant( $args, $instance );
301
			}
302
		} else {
303
			$this->widget_non_instant( $args, $instance );
304
		}
305
	}
306
307
	/**
308
	 * Render the non-instant frontend widget.
309
	 *
310
	 * @since 8.3.0
311
	 *
312
	 * @param array $args     Widgets args supplied by the theme.
313
	 * @param array $instance The current widget instance.
314
	 */
315
	public function widget_non_instant( $args, $instance ) {
316
		$display_filters = false;
317
318
		if ( is_search() ) {
319
			if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
320
				Jetpack_Search::instance()->update_search_results_aggregations();
321
			}
322
323
			$filters = Jetpack_Search::instance()->get_filters();
324
325 View Code Duplication
			if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
326
				$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
327
			}
328
329
			if ( ! empty( $filters ) ) {
330
				$display_filters = true;
331
			}
332
		}
333
334
		if ( ! $display_filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
335
			return;
336
		}
337
338
		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
339
340
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
341
		$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...
342
343
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
344
		?>
345
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" >
346
		<?php
347
348
		if ( ! empty( $title ) ) {
349
			/**
350
			 * Responsible for displaying the title of the Jetpack Search filters widget.
351
			 *
352
			 * @module search
353
			 *
354
			 * @since  5.7.0
355
			 *
356
			 * @param string $title                The widget's title
357
			 * @param string $args['before_title'] The HTML tag to display before the title
358
			 * @param string $args['after_title']  The HTML tag to display after the title
359
			 */
360
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
361
		}
362
363
		$default_sort            = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
364
		list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
365
		$current_sort            = "{$orderby}|{$order}";
366
367
		// we need to dynamically inject the sort field into the search box when the search box is enabled, and display
368
		// it separately when it's not.
369
		if ( ! empty( $instance['search_box_enabled'] ) ) {
370
			Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
371
		}
372
373
		if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
374
				?>
375
					<div class="jetpack-search-sort-wrapper">
376
				<label>
377
					<?php esc_html_e( 'Sort by', 'jetpack' ); ?>
378
					<select class="jetpack-search-sort">
379 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
380
							<option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
381
								<?php echo esc_html( $label ); ?>
382
							</option>
383
						<?php } ?>
384
					</select>
385
				</label>
386
			</div>
387
		<?php
388
		endif;
389
390
		if ( $display_filters ) {
391
			/**
392
			 * Responsible for rendering filters to narrow down search results.
393
			 *
394
			 * @module search
395
			 *
396
			 * @since  5.8.0
397
			 *
398
			 * @param array $filters    The possible filters for the current query.
399
			 * @param array $post_types An array of post types to limit filtering to.
400
			 */
401
			do_action(
402
				'jetpack_search_render_filters',
403
				$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...
404
				isset( $instance['post_types'] ) ? $instance['post_types'] : null
405
			);
406
		}
407
408
		$this->maybe_render_sort_javascript( $instance, $order, $orderby );
409
410
		echo '</div>';
411
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
412
	}
413
414
	/**
415
	 * Render the instant frontend widget.
416
	 *
417
	 * @since 8.3.0
418
	 *
419
	 * @param array $args     Widgets args supplied by the theme.
420
	 * @param array $instance The current widget instance.
421
	 */
422
	public function widget_instant( $args, $instance ) {
423
		if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
424
			Jetpack_Search::instance()->update_search_results_aggregations();
425
		}
426
427
		$filters = Jetpack_Search::instance()->get_filters();
428 View Code Duplication
		if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
429
			$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
430
		}
431
432
		$display_filters = ! empty( $filters );
433
434
		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
435
436
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
437
		$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...
438
439
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
440
		?>
441
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
442
		<?php
443
444
		if ( ! empty( $title ) ) {
445
			/**
446
			 * Responsible for displaying the title of the Jetpack Search filters widget.
447
			 *
448
			 * @module search
449
			 *
450
			 * @since  5.7.0
451
			 *
452
			 * @param string $title                The widget's title
453
			 * @param string $args['before_title'] The HTML tag to display before the title
454
			 * @param string $args['after_title']  The HTML tag to display after the title
455
			 */
456
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
457
		}
458
459
		Jetpack_Search_Template_Tags::render_widget_search_form( array(), '', '' );
460
461
		if ( $display_filters ) {
462
			/**
463
			 * Responsible for rendering filters to narrow down search results.
464
			 *
465
			 * @module search
466
			 *
467
			 * @since  5.8.0
468
			 *
469
			 * @param array $filters    The possible filters for the current query.
470
			 * @param array $post_types An array of post types to limit filtering to.
471
			 */
472
			do_action(
473
				'jetpack_search_render_filters',
474
				$filters,
475
				null
476
			);
477
		}
478
479
		echo '</div>';
480
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
481
	}
482
483
	/**
484
	 * Render the instant widget for the overlay.
485
	 *
486
	 * @since 8.3.0
487
	 *
488
	 * @param array $args     Widgets args supplied by the theme.
489
	 * @param array $instance The current widget instance.
490
	 */
491
	public function widget_empty_instant( $args, $instance ) {
492
		$title = isset( $instance['title'] ) ? $instance['title'] : '';
493
494
		if ( empty( $title ) ) {
495
			$title = '';
496
		}
497
498
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
499
		$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...
500
501
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
502
		?>
503
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
504
		<?php
505
506
		if ( ! empty( $title ) ) {
507
			/**
508
			 * Responsible for displaying the title of the Jetpack Search filters widget.
509
			 *
510
			 * @module search
511
			 *
512
			 * @since  5.7.0
513
			 *
514
			 * @param string $title                The widget's title
515
			 * @param string $args['before_title'] The HTML tag to display before the title
516
			 * @param string $args['after_title']  The HTML tag to display after the title
517
			 */
518
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
519
		}
520
521
		echo '</div>';
522
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
523
	}
524
525
526
	/**
527
	 * Renders JavaScript for the sorting controls on the frontend.
528
	 *
529
	 * This JS is a bit complicated, but here's what it's trying to do:
530
	 * - find the search form
531
	 * - find the orderby/order fields and set default values
532
	 * - detect changes to the sort field, if it exists, and use it to set the order field values
533
	 *
534
	 * @since 5.8.0
535
	 *
536
	 * @param array  $instance The current widget instance.
537
	 * @param string $order    The order to initialize the select with.
538
	 * @param string $orderby  The orderby to initialize the select with.
539
	 */
540
	private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
541
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
542
			return;
543
		}
544
545
		if ( ! empty( $instance['user_sort_enabled'] ) ) :
546
		?>
547
		<script type="text/javascript">
548
			var jetpackSearchModuleSorting = function() {
549
				var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
550
					orderDefault   = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
551
					widgetId       = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
552
					searchQuery    = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
553
					isSearch       = <?php echo (int) is_search(); ?>;
554
555
				var container = document.getElementById( widgetId + '-wrapper' ),
556
					form = container.querySelector( '.jetpack-search-form form' ),
557
					orderBy = form.querySelector( 'input[name=orderby]' ),
558
					order = form.querySelector( 'input[name=order]' ),
559
					searchInput = form.querySelector( 'input[name="s"]' ),
560
					sortSelectInput = container.querySelector( '.jetpack-search-sort' );
561
562
				orderBy.value = orderByDefault;
563
				order.value = orderDefault;
564
565
				// Some themes don't set the search query, which results in the query being lost
566
				// when doing a sort selection. So, if the query isn't set, let's set it now. This approach
567
				// is chosen over running a regex over HTML for every search query performed.
568
				if ( isSearch && ! searchInput.value ) {
569
					searchInput.value = searchQuery;
570
				}
571
572
				searchInput.classList.add( 'show-placeholder' );
573
574
				sortSelectInput.addEventListener( 'change', function( event ) {
575
					var values  = event.target.value.split( '|' );
576
					orderBy.value = values[0];
577
					order.value = values[1];
578
579
					form.submit();
580
				} );
581
			}
582
583
			if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
584
				jetpackSearchModuleSorting();
585
			} else {
586
				document.addEventListener( 'DOMContentLoaded', jetpackSearchModuleSorting );
587
			}
588
			</script>
589
		<?php
590
		endif;
591
	}
592
593
	/**
594
	 * Convert a sort string into the separate order by and order parts.
595
	 *
596
	 * @since 5.8.0
597
	 *
598
	 * @param string $sort A sort string.
599
	 *
600
	 * @return array Order by and order.
601
	 */
602
	private function sorting_to_wp_query_param( $sort ) {
603
		$parts   = explode( '|', $sort );
604
		$orderby = isset( $_GET['orderby'] )
605
			? $_GET['orderby']
606
			: $parts[0];
607
608
		$order = isset( $_GET['order'] )
609
			? strtoupper( $_GET['order'] )
610
			: ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
611
612
		return array( $orderby, $order );
613
	}
614
615
	/**
616
	 * Updates a particular instance of the widget. Validates and sanitizes the options.
617
	 *
618
	 * @since 5.0.0
619
	 *
620
	 * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
621
	 * @param array $old_instance Old settings for this instance.
622
	 *
623
	 * @return array Settings to save.
624
	 */
625
	public function update( $new_instance, $old_instance ) {
626
		$instance = array();
627
628
		$instance['title']              = sanitize_text_field( $new_instance['title'] );
629
		$instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
630
		$instance['user_sort_enabled']  = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
631
		$instance['sort']               = $new_instance['sort'];
632
		$instance['post_types']         = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
633
			? array()
634
			: array_map( 'sanitize_key', $new_instance['post_types'] );
635
636
		$filters = array();
637
		if ( isset( $new_instance['filter_type'] ) ) {
638
			foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
639
				$count = (int) $new_instance['num_filters'][ $index ];
640
				$count = min( 50, $count ); // Set max boundary at 50.
641
				$count = max( 1, $count );  // Set min boundary at 1.
642
643
				switch ( $type ) {
644
					case 'taxonomy':
645
						$filters[] = array(
646
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
647
							'type'     => 'taxonomy',
648
							'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
649
							'count'    => $count,
650
						);
651
						break;
652
					case 'post_type':
653
						$filters[] = array(
654
							'name'  => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
655
							'type'  => 'post_type',
656
							'count' => $count,
657
						);
658
						break;
659
					case 'date_histogram':
660
						$filters[] = array(
661
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
662
							'type'     => 'date_histogram',
663
							'count'    => $count,
664
							'field'    => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
665
							'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
666
						);
667
						break;
668
				}
669
			}
670
		}
671
672
		if ( ! empty( $filters ) ) {
673
			$instance['filters'] = $filters;
674
		}
675
676
		return $instance;
677
	}
678
679
	/**
680
	 * Outputs the settings update form.
681
	 *
682
	 * @since 5.0.0
683
	 *
684
	 * @param array $instance Previously saved values from database.
685
	 */
686
	public function form( $instance ) {
687
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
688
			return $this->form_for_instant_search( $instance );
689
		}
690
691
		$instance = $this->jetpack_search_populate_defaults( $instance );
692
693
		$title = strip_tags( $instance['title'] );
694
695
		$hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
696
697
		$classes = sprintf(
698
			'jetpack-search-filters-widget %s %s %s',
699
			$hide_filters ? 'hide-filters' : '',
700
			$instance['search_box_enabled'] ? '' : 'hide-post-types',
701
			$this->id
702
		);
703
		?>
704
		<div class="<?php echo esc_attr( $classes ); ?>">
705
			<p>
706
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
707
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
708
				</label>
709
				<input
710
					class="widefat"
711
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
712
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
713
					type="text"
714
					value="<?php echo esc_attr( $title ); ?>"
715
				/>
716
			</p>
717
718
			<p>
719
				<label>
720
					<input
721
						type="checkbox"
722
						class="jetpack-search-filters-widget__search-box-enabled"
723
						name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
724
						<?php checked( $instance['search_box_enabled'] ); ?>
725
					/>
726
					<?php esc_html_e( 'Show search box', 'jetpack' ); ?>
727
				</label>
728
			</p>
729
730
			<p>
731
				<label>
732
					<input
733
						type="checkbox"
734
						class="jetpack-search-filters-widget__sort-controls-enabled"
735
						name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
736
						<?php checked( $instance['user_sort_enabled'] ); ?>
737
						<?php disabled( ! $instance['search_box_enabled'] ); ?>
738
					/>
739
					<?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
740
				</label>
741
			</p>
742
743
			<p class="jetpack-search-filters-widget__post-types-select">
744
				<label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
745
				<?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
746
					<label>
747
						<input
748
							type="checkbox"
749
							value="<?php echo esc_attr( $post_type->name ); ?>"
750
							name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
751
							<?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
752
						/>&nbsp;
753
						<?php echo esc_html( $post_type->label ); ?>
754
					</label>
755
				<?php endforeach; ?>
756
			</p>
757
758
			<p>
759
				<label>
760
					<?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
761
					<select
762
						name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
763
						class="widefat jetpack-search-filters-widget__sort-order">
764 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
765
							<option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
766
								<?php echo esc_html( $label ); ?>
767
							</option>
768
						<?php } ?>
769
					</select>
770
				</label>
771
			</p>
772
773
			<?php if ( ! $hide_filters ) : ?>
774
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
775
					<?php echo $this->render_widget_edit_filter( array(), true ); ?>
776
				</script>
777
				<div class="jetpack-search-filters-widget__filters">
778
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
779
						<?php $this->render_widget_edit_filter( $filter ); ?>
780
					<?php endforeach; ?>
781
				</div>
782
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
783
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
784
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
785
					</a>
786
				</p>
787
				<noscript>
788
					<p class="jetpack-search-filters-help">
789
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
790
					</p>
791
				</noscript>
792
				<?php if ( is_customize_preview() ) : ?>
793
					<p class="jetpack-search-filters-help">
794
						<a href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-search', array( 'anchor' => 'filters-not-showing-up' ) ) ); ?>" target="_blank">
795
							<?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
796
						</a>
797
					</p>
798
				<?php endif; ?>
799
			<?php endif; ?>
800
		</div>
801
		<?php
802
	}
803
804
	/**
805
	 * Outputs the widget update form to be used in the Customizer for Instant Search.
806
	 *
807
	 * @since 8.6.0
808
	 *
809
	 * @param array $instance Previously saved values from database.
810
	 */
811
	private function form_for_instant_search( $instance ) {
812
		$instance = $this->populate_defaults_for_instant_search( $instance );
813
		$classes  = sprintf( 'jetpack-search-filters-widget %s', $this->id );
814
815
		?>
816
		<div class="<?php echo esc_attr( $classes ); ?>">
817
			<!-- Title control -->
818
			<p>
819
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
820
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
821
				</label>
822
				<input
823
					class="widefat"
824
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
825
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
826
					type="text"
827
					value="<?php echo esc_attr( wp_strip_all_tags( $instance['title'] ) ); ?>"
828
				/>
829
			</p>
830
831
			<!-- Filters control -->
832
			<?php if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() ) : ?>
833
				<div class="jetpack-search-filters-widget__filters">
834
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
835
						<?php $this->render_widget_edit_filter( $filter ); ?>
836
					<?php endforeach; ?>
837
				</div>
838
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
839
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
840
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
841
					</a>
842
				</p>
843
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
844
					<?php $this->render_widget_edit_filter( array(), true ); ?>
845
				</script>
846
				<noscript>
847
					<p class="jetpack-search-filters-help">
848
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
849
					</p>
850
				</noscript>
851
			<?php endif; ?>
852
		</div>
853
		<?php
854
	}
855
856
	/**
857
	 * We need to render HTML in two formats: an Underscore template (client-side)
858
	 * and native PHP (server-side). This helper function allows for easy rendering
859
	 * of attributes in both formats.
860
	 *
861
	 * @since 5.8.0
862
	 *
863
	 * @param string $name        Attribute name.
864
	 * @param string $value       Attribute value.
865
	 * @param bool   $is_template Whether this is for an Underscore template or not.
866
	 */
867
	private function render_widget_attr( $name, $value, $is_template ) {
868
		echo $is_template ? "<%= $name %>" : esc_attr( $value );
869
	}
870
871
	/**
872
	 * We need to render HTML in two formats: an Underscore template (client-size)
873
	 * and native PHP (server-side). This helper function allows for easy rendering
874
	 * of the "selected" attribute in both formats.
875
	 *
876
	 * @since 5.8.0
877
	 *
878
	 * @param string $name        Attribute name.
879
	 * @param string $value       Attribute value.
880
	 * @param string $compare     Value to compare to the attribute value to decide if it should be selected.
881
	 * @param bool   $is_template Whether this is for an Underscore template or not.
882
	 */
883
	private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
884
		$compare_js = rawurlencode( $compare );
885
		echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
886
	}
887
888
	/**
889
	 * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
890
	 *
891
	 * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
892
	 *
893
	 * @since 5.7.0
894
	 *
895
	 * @param array $filter      The filter to render.
896
	 * @param bool  $is_template Whether this is for an Underscore template or not.
897
	 */
898
	public function render_widget_edit_filter( $filter, $is_template = false ) {
899
		$args = wp_parse_args(
900
			$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...
901
				'name'      => '',
902
				'type'      => 'taxonomy',
903
				'taxonomy'  => '',
904
				'post_type' => '',
905
				'field'     => '',
906
				'interval'  => '',
907
				'count'     => self::DEFAULT_FILTER_COUNT,
908
			)
909
		);
910
911
		$args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
912
913
		?>
914
		<div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
915
			<p class="jetpack-search-filters-widget__type-select">
916
				<label>
917
					<?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
918
					<select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
919
						<option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
920
							<?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
921
						</option>
922
						<option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
923
							<?php esc_html_e( 'Post Type', 'jetpack' ); ?>
924
						</option>
925
						<option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
926
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
927
						</option>
928
					</select>
929
				</label>
930
			</p>
931
932
			<p class="jetpack-search-filters-widget__taxonomy-select">
933
				<label>
934
					<?php
935
						esc_html_e( 'Choose a taxonomy:', 'jetpack' );
936
						$seen_taxonomy_labels = array();
937
					?>
938
					<select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
939
						<?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
940
							<option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
941
								<?php
942
									$label = in_array( $taxonomy->label, $seen_taxonomy_labels )
943
										? sprintf(
944
											/* 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. */
945
											_x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
946
											$taxonomy->label,
947
											$taxonomy->name
948
										)
949
										: $taxonomy->label;
950
									echo esc_html( $label );
951
									$seen_taxonomy_labels[] = $taxonomy->label;
952
								?>
953
							</option>
954
						<?php endforeach; ?>
955
					</select>
956
				</label>
957
			</p>
958
959
			<p class="jetpack-search-filters-widget__date-histogram-select">
960
				<label>
961
					<?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
962
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
963
						<option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
964
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
965
						</option>
966
						<option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
967
							<?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
968
						</option>
969
						<option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
970
							<?php esc_html_e( 'Modified', 'jetpack' ); ?>
971
						</option>
972
						<option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
973
							<?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
974
						</option>
975
					</select>
976
				</label>
977
			</p>
978
979
			<p class="jetpack-search-filters-widget__date-histogram-select">
980
				<label>
981
					<?php esc_html_e( 'Choose an interval:', 'jetpack' ); ?>
982
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
983
						<option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
984
							<?php esc_html_e( 'Month', 'jetpack' ); ?>
985
						</option>
986
						<option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
987
							<?php esc_html_e( 'Year', 'jetpack' ); ?>
988
						</option>
989
					</select>
990
				</label>
991
			</p>
992
993
			<p class="jetpack-search-filters-widget__title">
994
				<label>
995
					<?php esc_html_e( 'Title:', 'jetpack' ); ?>
996
					<input
997
						class="widefat"
998
						type="text"
999
						name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
1000
						value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
1001
						placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
1002
					/>
1003
				</label>
1004
			</p>
1005
1006
			<p>
1007
				<label>
1008
					<?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
1009
					<input
1010
						class="widefat filter-count"
1011
						name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
1012
						type="number"
1013
						value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
1014
						min="1"
1015
						max="50"
1016
						step="1"
1017
						required
1018
					/>
1019
				</label>
1020
			</p>
1021
1022
			<p class="jetpack-search-filters-widget__controls">
1023
				<a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
1024
			</p>
1025
		</div>
1026
	<?php
1027
	}
1028
}
1029