Completed
Push — add/stats-package ( 873a22...c3aabb )
by
unknown
230:03 queued 222:25
created

Jetpack_Search_Widget::update()   C

Complexity

Conditions 11
Paths 64

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 64
nop 2
dl 0
loc 53
rs 6.8787
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Jetpack Search: Jetpack_Search_Widget class
4
 *
5
 * @package    Jetpack
6
 * @subpackage Jetpack Search
7
 * @since      5.0.0
8
 */
9
10
use Automattic\Jetpack\Constants;
11
use Automattic\Jetpack\Status;
12
use Automattic\Jetpack\Redirect;
13
14
add_action( 'widgets_init', 'jetpack_search_widget_init' );
15
16
function jetpack_search_widget_init() {
17
	if (
18
		! Jetpack::is_active()
19
		|| ( method_exists( 'Jetpack_Plan', 'supports' ) && ! Jetpack_Plan::supports( 'search' ) )
20
	) {
21
		return;
22
	}
23
24
	require_once JETPACK__PLUGIN_DIR . 'modules/search/class.jetpack-search-helpers.php';
25
	require_once JETPACK__PLUGIN_DIR . 'modules/search/class-jetpack-search-options.php';
26
27
	register_widget( 'Jetpack_Search_Widget' );
28
}
29
30
/**
31
 * Provides a widget to show available/selected filters on searches.
32
 *
33
 * @since 5.0.0
34
 *
35
 * @see   WP_Widget
36
 */
37
class Jetpack_Search_Widget extends WP_Widget {
38
39
	/**
40
	 * The Jetpack_Search instance.
41
	 *
42
	 * @since 5.7.0
43
	 * @var Jetpack_Search
44
	 */
45
	protected $jetpack_search;
46
47
	/**
48
	 * Number of aggregations (filters) to show by default.
49
	 *
50
	 * @since 5.8.0
51
	 * @var int
52
	 */
53
	const DEFAULT_FILTER_COUNT = 5;
54
55
	/**
56
	 * Default sort order for search results.
57
	 *
58
	 * @since 5.8.0
59
	 * @var string
60
	 */
61
	const DEFAULT_SORT = 'relevance_desc';
62
63
	/**
64
	 * Jetpack_Search_Widget constructor.
65
	 *
66
	 * @since 5.0.0
67
	 */
68
	public function __construct( $name = null ) {
69
		if ( empty( $name ) ) {
70
			$name = esc_html__( 'Search', 'jetpack' );
71
		}
72
		parent::__construct(
73
			Jetpack_Search_Helpers::FILTER_WIDGET_BASE,
74
			/** This filter is documented in modules/widgets/facebook-likebox.php */
75
			apply_filters( 'jetpack_widget_name', $name ),
76
			array(
77
				'classname'   => 'jetpack-filters widget_search',
78
				'description' => __( 'Instant search and filtering to help visitors quickly find relevant answers and explore your site.', 'jetpack' ),
79
			)
80
		);
81
82
		if (
83
			Jetpack_Search_Helpers::is_active_widget( $this->id ) &&
84
			! $this->is_search_active()
85
		) {
86
			$this->activate_search();
87
		}
88
89
		if ( is_admin() ) {
90
			add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
91
		} else {
92
			add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) );
93
		}
94
95
		add_action( 'jetpack_search_render_filters_widget_title', array( 'Jetpack_Search_Template_Tags', 'render_widget_title' ), 10, 3 );
96
		if ( Jetpack_Search_Options::is_instant_enabled() ) {
97
			add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_instant_filters' ), 10, 2 );
98
		} else {
99
			add_action( 'jetpack_search_render_filters', array( 'Jetpack_Search_Template_Tags', 'render_available_filters' ), 10, 2 );
100
		}
101
	}
102
103
	/**
104
	 * Check whether search is currently active
105
	 *
106
	 * @since 6.3
107
	 */
108
	public function is_search_active() {
109
		return Jetpack::is_module_active( 'search' );
110
	}
111
112
	/**
113
	 * Activate search
114
	 *
115
	 * @since 6.3
116
	 */
117
	public function activate_search() {
118
		Jetpack::activate_module( 'search', false, false );
119
	}
120
121
122
	/**
123
	 * Enqueues the scripts and styles needed for the customizer.
124
	 *
125
	 * @since 5.7.0
126
	 */
127
	public function widget_admin_setup() {
128
		wp_enqueue_style( 'widget-jetpack-search-filters', plugins_url( 'search/css/search-widget-admin-ui.css', __FILE__ ) );
129
130
		// Required for Tracks
131
		wp_register_script(
132
			'jp-tracks',
133
			'//stats.wp.com/w.js',
134
			array(),
135
			gmdate( 'YW' ),
136
			true
137
		);
138
139
		wp_register_script(
140
			'jp-tracks-functions',
141
			plugins_url( '_inc/lib/tracks/tracks-callables.js', JETPACK__PLUGIN_FILE ),
142
			array(),
143
			JETPACK__VERSION,
144
			false
145
		);
146
147
		wp_register_script(
148
			'jetpack-search-widget-admin',
149
			plugins_url( 'search/js/search-widget-admin.js', __FILE__ ),
150
			array( 'jquery', 'jquery-ui-sortable', 'jp-tracks', 'jp-tracks-functions' ),
151
			JETPACK__VERSION
152
		);
153
154
		wp_localize_script(
155
			'jetpack-search-widget-admin', 'jetpack_search_filter_admin', array(
156
				'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
157
				'tracksUserData'     => Jetpack_Tracks_Client::get_connected_user_tracks_identity(),
158
				'tracksEventData'    => array(
159
					'is_customizer' => (int) is_customize_preview(),
160
				),
161
				'i18n'               => array(
162
					'month'        => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', false ),
163
					'year'         => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', false ),
164
					'monthUpdated' => Jetpack_Search_Helpers::get_date_filter_type_name( 'month', true ),
165
					'yearUpdated'  => Jetpack_Search_Helpers::get_date_filter_type_name( 'year', true ),
166
				),
167
			)
168
		);
169
170
		wp_enqueue_script( 'jetpack-search-widget-admin' );
171
	}
172
173
	/**
174
	 * Enqueue scripts and styles for the frontend.
175
	 *
176
	 * @since 5.8.0
177
	 */
178
	public function enqueue_frontend_scripts() {
179
		if ( ! is_active_widget( false, false, $this->id_base, true ) || Jetpack_Search_Options::is_instant_enabled() ) {
180
			return;
181
		}
182
183
		wp_enqueue_script(
184
			'jetpack-search-widget',
185
			plugins_url( 'search/js/search-widget.js', __FILE__ ),
186
			array(),
187
			JETPACK__VERSION,
188
			true
189
		);
190
191
		wp_enqueue_style( 'jetpack-search-widget', plugins_url( 'search/css/search-widget-frontend.css', __FILE__ ) );
192
	}
193
194
	/**
195
	 * Get the list of valid sort types/orders.
196
	 *
197
	 * @since 5.8.0
198
	 *
199
	 * @return array The sort orders.
200
	 */
201
	private function get_sort_types() {
202
		return array(
203
			'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack' ) : esc_html__( 'Relevance', 'jetpack' ),
204
			'date|DESC'      => esc_html__( 'Newest first', 'jetpack' ),
205
			'date|ASC'       => esc_html__( 'Oldest first', 'jetpack' ),
206
		);
207
	}
208
209
	/**
210
	 * Callback for an array_filter() call in order to only get filters for the current widget.
211
	 *
212
	 * @see   Jetpack_Search_Widget::widget()
213
	 *
214
	 * @since 5.7.0
215
	 *
216
	 * @param array $item Filter item.
217
	 *
218
	 * @return bool Whether the current filter item is for the current widget.
219
	 */
220
	function is_for_current_widget( $item ) {
221
		return isset( $item['widget_id'] ) && $this->id == $item['widget_id'];
222
	}
223
224
	/**
225
	 * This method returns a boolean for whether the widget should show site-wide filters for the site.
226
	 *
227
	 * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
228
	 * configured filters via `Jetpack_Search::set_filters()`.
229
	 *
230
	 * @since 5.7.0
231
	 *
232
	 * @return bool Whether the widget should display site-wide filters or not.
233
	 */
234
	public function should_display_sitewide_filters() {
235
		$filter_widgets = get_option( 'widget_jetpack-search-filters' );
236
237
		// This shouldn't be empty, but just for sanity
238
		if ( empty( $filter_widgets ) ) {
239
			return false;
240
		}
241
242
		// If any widget has any filters, return false
243
		foreach ( $filter_widgets as $number => $widget ) {
244
			$widget_id = sprintf( '%s-%d', $this->id_base, $number );
245
			if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
246
				return false;
247
			}
248
		}
249
250
		return true;
251
	}
252
253
	public function jetpack_search_populate_defaults( $instance ) {
254
		$instance = wp_parse_args(
255
			(array) $instance, array(
0 ignored issues
show
Documentation introduced by
array('title' => '', 'se...post_types' => array()) is of type array<string,string|bool...,"post_types":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
256
				'title'              => '',
257
				'search_box_enabled' => true,
258
				'user_sort_enabled'  => true,
259
				'sort'               => self::DEFAULT_SORT,
260
				'filters'            => array( array() ),
261
				'post_types'         => array(),
262
			)
263
		);
264
265
		return $instance;
266
	}
267
268
	/**
269
	 * Populates the instance array with appropriate default values.
270
	 *
271
	 * @since 8.6.0
272
	 * @param array $instance Previously saved values from database.
273
	 * @return array Instance array with default values approprate for instant search
274
	 */
275
	public function populate_defaults_for_instant_search( $instance ) {
276
		return wp_parse_args(
277
			(array) $instance,
278
			array(
0 ignored issues
show
Documentation introduced by
array('title' => '', 'so...post_types' => array()) is of type array<string,string|arra...,"post_types":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

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