Completed
Push — fix/sync-empty-errors ( 53827c...ec13f3 )
by
unknown
89:12 queued 80:44
created

Jetpack_Search_Widget   F

Complexity

Total Complexity 97

Size/Duplication

Total Lines 1005
Duplicated Lines 1.59 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 16
loc 1005
rs 1.58
c 0
b 0
f 0
wmc 97
lcom 1
cbo 8

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 45 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
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' => '', '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...
279
				'title'   => '',
280
				'filters' => array(),
281
			)
282
		);
283
	}
284
285
	/**
286
	 * Responsible for rendering the widget on the frontend.
287
	 *
288
	 * @since 5.0.0
289
	 *
290
	 * @param array $args     Widgets args supplied by the theme.
291
	 * @param array $instance The current widget instance.
292
	 */
293
	public function widget( $args, $instance ) {
294
		$instance = $this->jetpack_search_populate_defaults( $instance );
295
296
		if ( ( new Status() )->is_offline_mode() ) {
297
			echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
298
			?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
299
				<div class="jetpack-search-sort-wrapper">
300
					<label>
301
						<?php esc_html_e( 'Jetpack Search not supported in Offline Mode', 'jetpack' ); ?>
302
					</label>
303
				</div>
304
			</div><?php
305
			echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
306
			return;
307
		}
308
309
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
310
			if ( 'jetpack-instant-search-sidebar' === $args['id'] ) {
311
				$this->widget_empty_instant( $args, $instance );
312
			} else {
313
				$this->widget_instant( $args, $instance );
314
			}
315
		} else {
316
			$this->widget_non_instant( $args, $instance );
317
		}
318
	}
319
320
	/**
321
	 * Render the non-instant frontend widget.
322
	 *
323
	 * @since 8.3.0
324
	 *
325
	 * @param array $args     Widgets args supplied by the theme.
326
	 * @param array $instance The current widget instance.
327
	 */
328
	public function widget_non_instant( $args, $instance ) {
329
		$display_filters = false;
330
331
		if ( is_search() ) {
332
			if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
333
				Jetpack_Search::instance()->update_search_results_aggregations();
334
			}
335
336
			$filters = Jetpack_Search::instance()->get_filters();
337
338 View Code Duplication
			if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
339
				$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
340
			}
341
342
			if ( ! empty( $filters ) ) {
343
				$display_filters = true;
344
			}
345
		}
346
347
		if ( ! $display_filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
348
			return;
349
		}
350
351
		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
352
353
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
354
		$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...
355
356
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
357
		?>
358
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" >
359
		<?php
360
361
		if ( ! empty( $title ) ) {
362
			/**
363
			 * Responsible for displaying the title of the Jetpack Search filters widget.
364
			 *
365
			 * @module search
366
			 *
367
			 * @since  5.7.0
368
			 *
369
			 * @param string $title                The widget's title
370
			 * @param string $args['before_title'] The HTML tag to display before the title
371
			 * @param string $args['after_title']  The HTML tag to display after the title
372
			 */
373
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
374
		}
375
376
		$default_sort            = isset( $instance['sort'] ) ? $instance['sort'] : self::DEFAULT_SORT;
377
		list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
378
		$current_sort            = "{$orderby}|{$order}";
379
380
		// we need to dynamically inject the sort field into the search box when the search box is enabled, and display
381
		// it separately when it's not.
382
		if ( ! empty( $instance['search_box_enabled'] ) ) {
383
			Jetpack_Search_Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
384
		}
385
386
		if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
387
				?>
388
					<div class="jetpack-search-sort-wrapper">
389
				<label>
390
					<?php esc_html_e( 'Sort by', 'jetpack' ); ?>
391
					<select class="jetpack-search-sort">
392 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
393
							<option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
394
								<?php echo esc_html( $label ); ?>
395
							</option>
396
						<?php } ?>
397
					</select>
398
				</label>
399
			</div>
400
		<?php
401
		endif;
402
403
		if ( $display_filters ) {
404
			/**
405
			 * Responsible for rendering filters to narrow down search results.
406
			 *
407
			 * @module search
408
			 *
409
			 * @since  5.8.0
410
			 *
411
			 * @param array $filters    The possible filters for the current query.
412
			 * @param array $post_types An array of post types to limit filtering to.
413
			 */
414
			do_action(
415
				'jetpack_search_render_filters',
416
				$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...
417
				isset( $instance['post_types'] ) ? $instance['post_types'] : null
418
			);
419
		}
420
421
		$this->maybe_render_sort_javascript( $instance, $order, $orderby );
422
423
		echo '</div>';
424
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
425
	}
426
427
	/**
428
	 * Render the instant frontend widget.
429
	 *
430
	 * @since 8.3.0
431
	 *
432
	 * @param array $args     Widgets args supplied by the theme.
433
	 * @param array $instance The current widget instance.
434
	 */
435
	public function widget_instant( $args, $instance ) {
436
		if ( Jetpack_Search_Helpers::should_rerun_search_in_customizer_preview() ) {
437
			Jetpack_Search::instance()->update_search_results_aggregations();
438
		}
439
440
		$filters = Jetpack_Search::instance()->get_filters();
441 View Code Duplication
		if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
442
			$filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
443
		}
444
445
		$display_filters = ! empty( $filters );
446
447
		$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
448
449
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
450
		$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...
451
452
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
453
		?>
454
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
455
		<?php
456
457
		if ( ! empty( $title ) ) {
458
			/**
459
			 * Responsible for displaying the title of the Jetpack Search filters widget.
460
			 *
461
			 * @module search
462
			 *
463
			 * @since  5.7.0
464
			 *
465
			 * @param string $title                The widget's title
466
			 * @param string $args['before_title'] The HTML tag to display before the title
467
			 * @param string $args['after_title']  The HTML tag to display after the title
468
			 */
469
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
470
		}
471
472
		Jetpack_Search_Template_Tags::render_widget_search_form( array(), '', '' );
473
474
		if ( $display_filters ) {
475
			/**
476
			 * Responsible for rendering filters to narrow down search results.
477
			 *
478
			 * @module search
479
			 *
480
			 * @since  5.8.0
481
			 *
482
			 * @param array $filters    The possible filters for the current query.
483
			 * @param array $post_types An array of post types to limit filtering to.
484
			 */
485
			do_action(
486
				'jetpack_search_render_filters',
487
				$filters,
488
				null
489
			);
490
		}
491
492
		echo '</div>';
493
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
494
	}
495
496
	/**
497
	 * Render the instant widget for the overlay.
498
	 *
499
	 * @since 8.3.0
500
	 *
501
	 * @param array $args     Widgets args supplied by the theme.
502
	 * @param array $instance The current widget instance.
503
	 */
504
	public function widget_empty_instant( $args, $instance ) {
505
		$title = isset( $instance['title'] ) ? $instance['title'] : '';
506
507
		if ( empty( $title ) ) {
508
			$title = '';
509
		}
510
511
		/** This filter is documented in core/src/wp-includes/default-widgets.php */
512
		$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...
513
514
		echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
515
		?>
516
			<div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
517
		<?php
518
519
		if ( ! empty( $title ) ) {
520
			/**
521
			 * Responsible for displaying the title of the Jetpack Search filters widget.
522
			 *
523
			 * @module search
524
			 *
525
			 * @since  5.7.0
526
			 *
527
			 * @param string $title                The widget's title
528
			 * @param string $args['before_title'] The HTML tag to display before the title
529
			 * @param string $args['after_title']  The HTML tag to display after the title
530
			 */
531
			do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
532
		}
533
534
		echo '</div>';
535
		echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
536
	}
537
538
539
	/**
540
	 * Renders JavaScript for the sorting controls on the frontend.
541
	 *
542
	 * This JS is a bit complicated, but here's what it's trying to do:
543
	 * - find the search form
544
	 * - find the orderby/order fields and set default values
545
	 * - detect changes to the sort field, if it exists, and use it to set the order field values
546
	 *
547
	 * @since 5.8.0
548
	 *
549
	 * @param array  $instance The current widget instance.
550
	 * @param string $order    The order to initialize the select with.
551
	 * @param string $orderby  The orderby to initialize the select with.
552
	 */
553
	private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
554
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
555
			return;
556
		}
557
558
		if ( ! empty( $instance['user_sort_enabled'] ) ) :
559
		?>
560
		<script type="text/javascript">
561
			var jetpackSearchModuleSorting = function() {
562
				var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
563
					orderDefault   = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
564
					widgetId       = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
565
					searchQuery    = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
566
					isSearch       = <?php echo (int) is_search(); ?>;
567
568
				var container = document.getElementById( widgetId + '-wrapper' ),
569
					form = container.querySelector( '.jetpack-search-form form' ),
570
					orderBy = form.querySelector( 'input[name=orderby]' ),
571
					order = form.querySelector( 'input[name=order]' ),
572
					searchInput = form.querySelector( 'input[name="s"]' ),
573
					sortSelectInput = container.querySelector( '.jetpack-search-sort' );
574
575
				orderBy.value = orderByDefault;
576
				order.value = orderDefault;
577
578
				// Some themes don't set the search query, which results in the query being lost
579
				// when doing a sort selection. So, if the query isn't set, let's set it now. This approach
580
				// is chosen over running a regex over HTML for every search query performed.
581
				if ( isSearch && ! searchInput.value ) {
582
					searchInput.value = searchQuery;
583
				}
584
585
				searchInput.classList.add( 'show-placeholder' );
586
587
				sortSelectInput.addEventListener( 'change', function( event ) {
588
					var values  = event.target.value.split( '|' );
589
					orderBy.value = values[0];
590
					order.value = values[1];
591
592
					form.submit();
593
				} );
594
			}
595
596
			if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
597
				jetpackSearchModuleSorting();
598
			} else {
599
				document.addEventListener( 'DOMContentLoaded', jetpackSearchModuleSorting );
600
			}
601
			</script>
602
		<?php
603
		endif;
604
	}
605
606
	/**
607
	 * Convert a sort string into the separate order by and order parts.
608
	 *
609
	 * @since 5.8.0
610
	 *
611
	 * @param string $sort A sort string.
612
	 *
613
	 * @return array Order by and order.
614
	 */
615
	private function sorting_to_wp_query_param( $sort ) {
616
		$parts   = explode( '|', $sort );
617
		$orderby = isset( $_GET['orderby'] )
618
			? $_GET['orderby']
619
			: $parts[0];
620
621
		$order = isset( $_GET['order'] )
622
			? strtoupper( $_GET['order'] )
623
			: ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
624
625
		return array( $orderby, $order );
626
	}
627
628
	/**
629
	 * Updates a particular instance of the widget. Validates and sanitizes the options.
630
	 *
631
	 * @since 5.0.0
632
	 *
633
	 * @param array $new_instance New settings for this instance as input by the user via Jetpack_Search_Widget::form().
634
	 * @param array $old_instance Old settings for this instance.
635
	 *
636
	 * @return array Settings to save.
637
	 */
638
	public function update( $new_instance, $old_instance ) {
639
		$instance = array();
640
641
		$instance['title']              = sanitize_text_field( $new_instance['title'] );
642
		$instance['search_box_enabled'] = empty( $new_instance['search_box_enabled'] ) ? '0' : '1';
643
		$instance['user_sort_enabled']  = empty( $new_instance['user_sort_enabled'] ) ? '0' : '1';
644
		$instance['sort']               = $new_instance['sort'];
645
		$instance['post_types']         = empty( $new_instance['post_types'] ) || empty( $instance['search_box_enabled'] )
646
			? array()
647
			: array_map( 'sanitize_key', $new_instance['post_types'] );
648
649
		$filters = array();
650
		if ( isset( $new_instance['filter_type'] ) ) {
651
			foreach ( (array) $new_instance['filter_type'] as $index => $type ) {
652
				$count = intval( $new_instance['num_filters'][ $index ] );
653
				$count = min( 50, $count ); // Set max boundary at 50.
654
				$count = max( 1, $count );  // Set min boundary at 1.
655
656
				switch ( $type ) {
657
					case 'taxonomy':
658
						$filters[] = array(
659
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
660
							'type'     => 'taxonomy',
661
							'taxonomy' => sanitize_key( $new_instance['taxonomy_type'][ $index ] ),
662
							'count'    => $count,
663
						);
664
						break;
665
					case 'post_type':
666
						$filters[] = array(
667
							'name'  => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
668
							'type'  => 'post_type',
669
							'count' => $count,
670
						);
671
						break;
672
					case 'date_histogram':
673
						$filters[] = array(
674
							'name'     => sanitize_text_field( $new_instance['filter_name'][ $index ] ),
675
							'type'     => 'date_histogram',
676
							'count'    => $count,
677
							'field'    => sanitize_key( $new_instance['date_histogram_field'][ $index ] ),
678
							'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ),
679
						);
680
						break;
681
				}
682
			}
683
		}
684
685
		if ( ! empty( $filters ) ) {
686
			$instance['filters'] = $filters;
687
		}
688
689
		return $instance;
690
	}
691
692
	/**
693
	 * Outputs the settings update form.
694
	 *
695
	 * @since 5.0.0
696
	 *
697
	 * @param array $instance Previously saved values from database.
698
	 */
699
	public function form( $instance ) {
700
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
701
			return $this->form_for_instant_search( $instance );
702
		}
703
704
		$instance = $this->jetpack_search_populate_defaults( $instance );
705
706
		$title = strip_tags( $instance['title'] );
707
708
		$hide_filters = Jetpack_Search_Helpers::are_filters_by_widget_disabled();
709
710
		$classes = sprintf(
711
			'jetpack-search-filters-widget %s %s %s',
712
			$hide_filters ? 'hide-filters' : '',
713
			$instance['search_box_enabled'] ? '' : 'hide-post-types',
714
			$this->id
715
		);
716
		?>
717
		<div class="<?php echo esc_attr( $classes ); ?>">
718
			<p>
719
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
720
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
721
				</label>
722
				<input
723
					class="widefat"
724
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
725
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
726
					type="text"
727
					value="<?php echo esc_attr( $title ); ?>"
728
				/>
729
			</p>
730
731
			<p>
732
				<label>
733
					<input
734
						type="checkbox"
735
						class="jetpack-search-filters-widget__search-box-enabled"
736
						name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
737
						<?php checked( $instance['search_box_enabled'] ); ?>
738
					/>
739
					<?php esc_html_e( 'Show search box', 'jetpack' ); ?>
740
				</label>
741
			</p>
742
743
			<p>
744
				<label>
745
					<input
746
						type="checkbox"
747
						class="jetpack-search-filters-widget__sort-controls-enabled"
748
						name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
749
						<?php checked( $instance['user_sort_enabled'] ); ?>
750
						<?php disabled( ! $instance['search_box_enabled'] ); ?>
751
					/>
752
					<?php esc_html_e( 'Show sort selection dropdown', 'jetpack' ); ?>
753
				</label>
754
			</p>
755
756
			<p class="jetpack-search-filters-widget__post-types-select">
757
				<label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack' ); ?></label>
758
				<?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
759
					<label>
760
						<input
761
							type="checkbox"
762
							value="<?php echo esc_attr( $post_type->name ); ?>"
763
							name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
764
							<?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'] ) ); ?>
765
						/>&nbsp;
766
						<?php echo esc_html( $post_type->label ); ?>
767
					</label>
768
				<?php endforeach; ?>
769
			</p>
770
771
			<p>
772
				<label>
773
					<?php esc_html_e( 'Default sort order:', 'jetpack' ); ?>
774
					<select
775
						name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
776
						class="widefat jetpack-search-filters-widget__sort-order">
777 View Code Duplication
						<?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
778
							<option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
779
								<?php echo esc_html( $label ); ?>
780
							</option>
781
						<?php } ?>
782
					</select>
783
				</label>
784
			</p>
785
786
			<?php if ( ! $hide_filters ) : ?>
787
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
788
					<?php echo $this->render_widget_edit_filter( array(), true ); ?>
789
				</script>
790
				<div class="jetpack-search-filters-widget__filters">
791
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
792
						<?php $this->render_widget_edit_filter( $filter ); ?>
793
					<?php endforeach; ?>
794
				</div>
795
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
796
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
797
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
798
					</a>
799
				</p>
800
				<noscript>
801
					<p class="jetpack-search-filters-help">
802
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
803
					</p>
804
				</noscript>
805
				<?php if ( is_customize_preview() ) : ?>
806
					<p class="jetpack-search-filters-help">
807
						<a href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-search', array( 'anchor' => 'filters-not-showing-up' ) ) ); ?>" target="_blank">
808
							<?php esc_html_e( "Why aren't my filters appearing?", 'jetpack' ); ?>
809
						</a>
810
					</p>
811
				<?php endif; ?>
812
			<?php endif; ?>
813
		</div>
814
		<?php
815
	}
816
817
	/**
818
	 * Outputs the widget update form to be used in the Customizer for Instant Search.
819
	 *
820
	 * @since 8.6.0
821
	 *
822
	 * @param array $instance Previously saved values from database.
823
	 */
824
	private function form_for_instant_search( $instance ) {
825
		$instance = $this->populate_defaults_for_instant_search( $instance );
826
		$classes  = sprintf( 'jetpack-search-filters-widget %s', $this->id );
827
828
		?>
829
		<div class="<?php echo esc_attr( $classes ); ?>">
830
			<!-- Title control -->
831
			<p>
832
				<label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
833
					<?php esc_html_e( 'Title (optional):', 'jetpack' ); ?>
834
				</label>
835
				<input
836
					class="widefat"
837
					id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
838
					name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
839
					type="text"
840
					value="<?php echo esc_attr( wp_strip_all_tags( $instance['title'] ) ); ?>"
841
				/>
842
			</p>
843
844
			<!-- Filters control -->
845
			<?php if ( ! Jetpack_Search_Helpers::are_filters_by_widget_disabled() ) : ?>
846
				<div class="jetpack-search-filters-widget__filters">
847
					<?php foreach ( (array) $instance['filters'] as $filter ) : ?>
848
						<?php $this->render_widget_edit_filter( $filter ); ?>
849
					<?php endforeach; ?>
850
				</div>
851
				<p class="jetpack-search-filters-widget__add-filter-wrapper">
852
					<a class="button jetpack-search-filters-widget__add-filter" href="#">
853
						<?php esc_html_e( 'Add a filter', 'jetpack' ); ?>
854
					</a>
855
				</p>
856
				<script class="jetpack-search-filters-widget__filter-template" type="text/template">
857
					<?php $this->render_widget_edit_filter( array(), true ); ?>
858
				</script>
859
				<noscript>
860
					<p class="jetpack-search-filters-help">
861
						<?php echo esc_html_e( 'Adding filters requires JavaScript!', 'jetpack' ); ?>
862
					</p>
863
				</noscript>
864
			<?php endif; ?>
865
		</div>
866
		<?php
867
	}
868
869
	/**
870
	 * We need to render HTML in two formats: an Underscore template (client-side)
871
	 * and native PHP (server-side). This helper function allows for easy rendering
872
	 * of attributes in both formats.
873
	 *
874
	 * @since 5.8.0
875
	 *
876
	 * @param string $name        Attribute name.
877
	 * @param string $value       Attribute value.
878
	 * @param bool   $is_template Whether this is for an Underscore template or not.
879
	 */
880
	private function render_widget_attr( $name, $value, $is_template ) {
881
		echo $is_template ? "<%= $name %>" : esc_attr( $value );
882
	}
883
884
	/**
885
	 * We need to render HTML in two formats: an Underscore template (client-size)
886
	 * and native PHP (server-side). This helper function allows for easy rendering
887
	 * of the "selected" attribute in both formats.
888
	 *
889
	 * @since 5.8.0
890
	 *
891
	 * @param string $name        Attribute name.
892
	 * @param string $value       Attribute value.
893
	 * @param string $compare     Value to compare to the attribute value to decide if it should be selected.
894
	 * @param bool   $is_template Whether this is for an Underscore template or not.
895
	 */
896
	private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
897
		$compare_js = rawurlencode( $compare );
898
		echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare );
899
	}
900
901
	/**
902
	 * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
903
	 *
904
	 * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
905
	 *
906
	 * @since 5.7.0
907
	 *
908
	 * @param array $filter      The filter to render.
909
	 * @param bool  $is_template Whether this is for an Underscore template or not.
910
	 */
911
	public function render_widget_edit_filter( $filter, $is_template = false ) {
912
		$args = wp_parse_args(
913
			$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...
914
				'name'      => '',
915
				'type'      => 'taxonomy',
916
				'taxonomy'  => '',
917
				'post_type' => '',
918
				'field'     => '',
919
				'interval'  => '',
920
				'count'     => self::DEFAULT_FILTER_COUNT,
921
			)
922
		);
923
924
		$args['name_placeholder'] = Jetpack_Search_Helpers::generate_widget_filter_name( $args );
925
926
		?>
927
		<div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
928
			<p class="jetpack-search-filters-widget__type-select">
929
				<label>
930
					<?php esc_html_e( 'Filter Type:', 'jetpack' ); ?>
931
					<select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
932
						<option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
933
							<?php esc_html_e( 'Taxonomy', 'jetpack' ); ?>
934
						</option>
935
						<option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
936
							<?php esc_html_e( 'Post Type', 'jetpack' ); ?>
937
						</option>
938
						<option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
939
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
940
						</option>
941
					</select>
942
				</label>
943
			</p>
944
945
			<p class="jetpack-search-filters-widget__taxonomy-select">
946
				<label>
947
					<?php
948
						esc_html_e( 'Choose a taxonomy:', 'jetpack' );
949
						$seen_taxonomy_labels = array();
950
					?>
951
					<select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
952
						<?php foreach ( get_taxonomies( array( 'public' => true ), 'objects' ) as $taxonomy ) : ?>
953
							<option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
954
								<?php
955
									$label = in_array( $taxonomy->label, $seen_taxonomy_labels )
956
										? sprintf(
957
											/* 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. */
958
											_x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack' ),
959
											$taxonomy->label,
960
											$taxonomy->name
961
										)
962
										: $taxonomy->label;
963
									echo esc_html( $label );
964
									$seen_taxonomy_labels[] = $taxonomy->label;
965
								?>
966
							</option>
967
						<?php endforeach; ?>
968
					</select>
969
				</label>
970
			</p>
971
972
			<p class="jetpack-search-filters-widget__date-histogram-select">
973
				<label>
974
					<?php esc_html_e( 'Choose a field:', 'jetpack' ); ?>
975
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
976
						<option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
977
							<?php esc_html_e( 'Date', 'jetpack' ); ?>
978
						</option>
979
						<option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
980
							<?php esc_html_e( 'Date GMT', 'jetpack' ); ?>
981
						</option>
982
						<option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
983
							<?php esc_html_e( 'Modified', 'jetpack' ); ?>
984
						</option>
985
						<option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
986
							<?php esc_html_e( 'Modified GMT', 'jetpack' ); ?>
987
						</option>
988
					</select>
989
				</label>
990
			</p>
991
992
			<p class="jetpack-search-filters-widget__date-histogram-select">
993
				<label>
994
					<?php esc_html_e( 'Choose an interval:', 'jetpack' ); ?>
995
					<select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
996
						<option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
997
							<?php esc_html_e( 'Month', 'jetpack' ); ?>
998
						</option>
999
						<option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
1000
							<?php esc_html_e( 'Year', 'jetpack' ); ?>
1001
						</option>
1002
					</select>
1003
				</label>
1004
			</p>
1005
1006
			<p class="jetpack-search-filters-widget__title">
1007
				<label>
1008
					<?php esc_html_e( 'Title:', 'jetpack' ); ?>
1009
					<input
1010
						class="widefat"
1011
						type="text"
1012
						name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
1013
						value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
1014
						placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
1015
					/>
1016
				</label>
1017
			</p>
1018
1019
			<p>
1020
				<label>
1021
					<?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack' ); ?>
1022
					<input
1023
						class="widefat filter-count"
1024
						name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
1025
						type="number"
1026
						value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
1027
						min="1"
1028
						max="50"
1029
						step="1"
1030
						required
1031
					/>
1032
				</label>
1033
			</p>
1034
1035
			<p class="jetpack-search-filters-widget__controls">
1036
				<a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack' ); ?></a>
1037
			</p>
1038
		</div>
1039
	<?php
1040
	}
1041
}
1042