PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-search / src / widgets / class-search-widget.php
jetpack / jetpack_vendor / automattic / jetpack-search / src / widgets Last commit date
css 8 months ago js 4 years ago class-search-widget.php 1 month ago
class-search-widget.php
1178 lines
1 <?php
2 /**
3 * Jetpack Search widget.
4 *
5 * @package automattic/jetpack-search
6 */
7
8 namespace Automattic\Jetpack\Search;
9
10 use Automattic\Jetpack\Assets;
11 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12 use Automattic\Jetpack\Redirect;
13 use Automattic\Jetpack\Status;
14 use Automattic\Jetpack\Sync\Modules\Search as Search_Sync_Module;
15 use Automattic\Jetpack\Tracking;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit( 0 );
19 }
20
21 /**
22 * Provides a widget to show available/selected filters on searches.
23 *
24 * @since 5.0.0
25 *
26 * @see WP_Widget
27 */
28 class Search_Widget extends \WP_Widget {
29
30 /**
31 * Number of aggregations (filters) to show by default.
32 *
33 * @since 5.8.0
34 * @var int
35 */
36 const DEFAULT_FILTER_COUNT = 5;
37 /**
38 * Default sort order for search results.
39 *
40 * @since 5.8.0
41 * @var string
42 */
43 const DEFAULT_SORT = 'relevance_desc';
44 /**
45 * Never used.
46 *
47 * @since 5.7.0
48 * @deprecated 0.44.5
49 * @var null
50 */
51 protected $jetpack_search;
52 /**
53 * Module_Control instance
54 *
55 * @var Module_Control
56 */
57 protected $module_control;
58
59 /**
60 * Search_Widget constructor.
61 *
62 * @param string $name Widget name.
63 * @since 5.0.0
64 */
65 public function __construct( $name = null ) {
66 if ( empty( $name ) ) {
67 $name = esc_html__( 'Search (Jetpack)', 'jetpack-search-pkg' );
68 }
69 $this->module_control = new Module_Control();
70 parent::__construct(
71 Helper::FILTER_WIDGET_BASE,
72 $name,
73 array(
74 'classname' => 'jetpack-filters widget_search',
75 'description' => __( 'Instant search and filtering to help visitors quickly find relevant answers and explore your site.', 'jetpack-search-pkg' ),
76 )
77 );
78
79 if (
80 Helper::is_active_widget( $this->id ) &&
81 ! $this->is_search_active()
82 ) {
83 $this->activate_search();
84 }
85
86 if ( is_admin() ) {
87 add_action( 'sidebar_admin_setup', array( $this, 'widget_admin_setup' ) );
88 }
89
90 add_action( 'jetpack_search_render_filters_widget_title', array( 'Automattic\Jetpack\Search\Template_Tags', 'render_widget_title' ), 10, 3 );
91 if ( Options::is_instant_enabled() ) {
92 add_action( 'jetpack_search_render_filters', array( 'Automattic\Jetpack\Search\Template_Tags', 'render_instant_filters' ), 10, 2 );
93 } else {
94 add_action( 'jetpack_search_render_filters', array( 'Automattic\Jetpack\Search\Template_Tags', 'render_available_filters' ), 10, 2 );
95 }
96 }
97
98 /**
99 * Check whether search is currently active
100 *
101 * @since 6.3
102 */
103 public function is_search_active() {
104 return $this->module_control->is_active();
105 }
106
107 /**
108 * Activate search
109 *
110 * @since 6.3
111 */
112 public function activate_search() {
113 return $this->module_control->activate();
114 }
115
116 /**
117 * Enqueues the scripts and styles needed for the customizer.
118 *
119 * @since 5.7.0
120 */
121 public function widget_admin_setup() {
122 // Register jp-tracks and jp-tracks-functions.
123 Tracking::register_tracks_functions_scripts();
124
125 Assets::register_script(
126 'jetpack-search-widget-admin',
127 'js/search-widget-admin.js',
128 __FILE__,
129 array(
130 'in_footer' => true,
131 'textdomain' => 'jetpack-search-pkg',
132 'css_path' => 'css/search-widget-admin-ui.css',
133 'dependencies' => array( 'jquery', 'jquery-ui-sortable', 'jp-tracks-functions' ),
134 )
135 );
136
137 $dotcom_data = ( new Connection_Manager( Package::SLUG ) )->get_connected_user_data();
138
139 wp_localize_script(
140 'jetpack-search-widget-admin',
141 'jetpack_search_filter_admin',
142 array(
143 'defaultFilterCount' => self::DEFAULT_FILTER_COUNT,
144 'tracksUserData' => ! empty( $dotcom_data ) ? array(
145 'userid' => $dotcom_data['ID'],
146 'username' => $dotcom_data['login'],
147 ) : false,
148 'tracksEventData' => array(
149 'is_customizer' => (int) is_customize_preview(),
150 ),
151 'i18n' => array(
152 'month' => Helper::get_date_filter_type_name( 'month', false ),
153 'year' => Helper::get_date_filter_type_name( 'year', false ),
154 'monthUpdated' => Helper::get_date_filter_type_name( 'month', true ),
155 'yearUpdated' => Helper::get_date_filter_type_name( 'year', true ),
156 ),
157 )
158 );
159
160 Assets::enqueue_script( 'jetpack-search-widget-admin' );
161 }
162
163 /**
164 * Enqueue scripts and styles for the frontend.
165 *
166 * @since 5.8.0
167 */
168 public function enqueue_frontend_scripts() {
169 if ( Options::is_instant_enabled() ) {
170 return;
171 }
172 Assets::register_script(
173 'jetpack-search-widget',
174 'js/search-widget.js',
175 __FILE__,
176 array(
177 'in_footer' => true,
178 'textdomain' => 'jetpack-search-pkg',
179 // Jetpack the plugin would concatenated the style with other styles and minimize. And the style would be dequeued from WP.
180 // @see https://github.com/Automattic/jetpack/blob/b3de78dce3d88b0d9b283282a5b04515245c8057/projects/plugins/jetpack/tools/builder/frontend-css.js#L52.
181 // @see https://github.com/Automattic/jetpack/blob/bb1b6a9a9cfa98600441f8fa31c9f9c4ef9a04a5/projects/plugins/jetpack/class.jetpack.php#L106.
182 'css_path' => 'css/search-widget-frontend.css',
183 'enqueue' => true,
184 )
185 );
186 }
187
188 /**
189 * Get the list of valid sort types/orders.
190 *
191 * @return array The sort orders.
192 * @since 5.8.0
193 */
194 private function get_sort_types() {
195 return array(
196 'relevance|DESC' => is_admin() ? esc_html__( 'Relevance (recommended)', 'jetpack-search-pkg' ) : esc_html__( 'Relevance', 'jetpack-search-pkg' ),
197 'date|DESC' => esc_html__( 'Newest first', 'jetpack-search-pkg' ),
198 'date|ASC' => esc_html__( 'Oldest first', 'jetpack-search-pkg' ),
199 );
200 }
201
202 /**
203 * Callback for an array_filter() call in order to only get filters for the current widget.
204 *
205 * @param array $item Filter item.
206 *
207 * @return bool Whether the current filter item is for the current widget.
208 * @see Search_Widget::widget()
209 *
210 * @since 5.7.0
211 */
212 public function is_for_current_widget( $item ) {
213 return isset( $item['widget_id'] ) && $this->id == $item['widget_id']; // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual
214 }
215
216 /**
217 * This method returns a boolean for whether the widget should show site-wide filters for the site.
218 *
219 * This is meant to provide backwards-compatibility for VIP, and other professional plan users, that manually
220 * configured filters via `Automattic\Jetpack\Search\Classic_Search::set_filters()`.
221 *
222 * @return bool Whether the widget should display site-wide filters or not.
223 * @since 5.7.0
224 */
225 public function should_display_sitewide_filters() {
226 $filter_widgets = get_option( 'widget_jetpack-search-filters' );
227
228 // This shouldn't be empty, but just for sanity.
229 if ( empty( $filter_widgets ) ) {
230 return false;
231 }
232
233 // If any widget has any filters, return false.
234 foreach ( $filter_widgets as $number => $widget ) {
235 $widget_id = sprintf( '%s-%d', $this->id_base, $number );
236 if ( ! empty( $widget['filters'] ) && is_active_widget( false, $widget_id, $this->id_base ) ) {
237 return false;
238 }
239 }
240
241 return true;
242 }
243
244 /**
245 * Widget defaults.
246 *
247 * @param array $instance Previously saved values from database.
248 */
249 public function jetpack_search_populate_defaults( $instance ) {
250 $instance = wp_parse_args(
251 (array) $instance,
252 array(
253 'title' => '',
254 'search_box_enabled' => true,
255 'user_sort_enabled' => true,
256 'sort' => self::DEFAULT_SORT,
257 'filters' => array( array() ),
258 'post_types' => array(),
259 )
260 );
261
262 return $instance;
263 }
264
265 /**
266 * Populates the instance array with appropriate default values.
267 *
268 * @param array $instance Previously saved values from database.
269 * @return array Instance array with default values approprate for instant search
270 * @since 8.6.0
271 */
272 public function populate_defaults_for_instant_search( $instance ) {
273 return wp_parse_args(
274 (array) $instance,
275 array(
276 'title' => '',
277 'filters' => array(),
278 )
279 );
280 }
281
282 /**
283 * Responsible for rendering the widget on the frontend.
284 *
285 * @param array $args Widgets args supplied by the theme.
286 * @param array $instance The current widget instance.
287 * @since 5.0.0
288 */
289 public function widget( $args, $instance ) {
290 $instance = $this->jetpack_search_populate_defaults( $instance );
291
292 if ( ( new Status() )->is_offline_mode() ) {
293 echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
294 ?><div id="<?php echo esc_attr( $this->id ); ?>-wrapper">
295 <div class="jetpack-search-sort-wrapper">
296 <label>
297 <?php esc_html_e( 'Jetpack Search not supported in Offline Mode', 'jetpack-search-pkg' ); ?>
298 </label>
299 </div>
300 </div>
301 <?php
302 echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
303 return;
304 }
305
306 // Enqueue front end assets.
307 $this->enqueue_frontend_scripts();
308
309 if ( Options::is_instant_enabled() ) {
310 if ( array_key_exists( 'id', $args ) && Instant_Search::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 * @param array $args Widgets args supplied by the theme.
324 * @param array $instance The current widget instance.
325 * @since 8.3.0
326 */
327 public function widget_non_instant( $args, $instance ) {
328 $filters = array();
329
330 // Search instance must have been initialized before widget render.
331 if ( is_search() ) {
332 $search_instance = Inline_Search::get_instance_maybe_fallback_to_classic();
333 if ( Helper::should_rerun_search_in_customizer_preview() ) {
334 $search_instance->update_search_results_aggregations();
335 }
336
337 $filters = $search_instance->get_filters();
338
339 if ( ! Helper::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
340 $filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
341 }
342 }
343
344 if ( ! $filters && empty( $instance['search_box_enabled'] ) && empty( $instance['user_sort_enabled'] ) ) {
345 return;
346 }
347
348 $title = ! empty( $instance['title'] ) ? $instance['title'] : '';
349
350 /** This filter is documented in core/src/wp-includes/default-widgets.php */
351 $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
352
353 echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
354 ?>
355 <div id="<?php echo esc_attr( $this->id ); ?>-wrapper" >
356 <?php
357
358 if ( ! empty( $title ) ) {
359 /**
360 * Responsible for displaying the title of the Jetpack Search filters widget.
361 *
362 * @module search
363 *
364 * @param string $title The widget's title
365 * @param string $args['before_title'] The HTML tag to display before the title
366 * @param string $args['after_title'] The HTML tag to display after the title
367 *@since 5.7.0
368 */
369 do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
370 }
371
372 $default_sort = $instance['sort'] ?? self::DEFAULT_SORT;
373 list( $orderby, $order ) = $this->sorting_to_wp_query_param( $default_sort );
374 $current_sort = "{$orderby}|{$order}";
375
376 // we need to dynamically inject the sort field into the search box when the search box is enabled, and display
377 // it separately when it's not.
378 if ( ! empty( $instance['search_box_enabled'] ) ) {
379 Template_Tags::render_widget_search_form( $instance['post_types'], $orderby, $order );
380 }
381
382 if ( ! empty( $instance['search_box_enabled'] ) && ! empty( $instance['user_sort_enabled'] ) ) :
383 ?>
384 <div class="jetpack-search-sort-wrapper">
385 <label>
386 <?php esc_html_e( 'Sort by', 'jetpack-search-pkg' ); ?>
387 <select class="jetpack-search-sort">
388 <?php foreach ( $this->get_sort_types() as $sort => $label ) { ?>
389 <option value="<?php echo esc_attr( $sort ); ?>" <?php selected( $current_sort, $sort ); ?>>
390 <?php echo esc_html( $label ); ?>
391 </option>
392 <?php } ?>
393 </select>
394 </label>
395 </div>
396 <?php
397 endif;
398
399 if ( $filters ) {
400 /**
401 * Responsible for rendering filters to narrow down search results.
402 *
403 * @module search
404 *
405 * @param array $filters The possible filters for the current query.
406 * @param array $post_types An array of post types to limit filtering to.
407 *@since 5.8.0
408 */
409 do_action(
410 'jetpack_search_render_filters',
411 $filters,
412 $instance['post_types'] ?? null
413 );
414 }
415
416 $this->maybe_render_sort_javascript( $instance, $order, $orderby );
417
418 echo '</div>';
419 echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
420 }
421
422 /**
423 * Render the instant frontend widget.
424 *
425 * @param array $args Widgets args supplied by the theme.
426 * @param array $instance The current widget instance.
427 * @since 8.3.0
428 */
429 public function widget_instant( $args, $instance ) {
430 // Exit early if search instance has not been initialized.
431 if ( ! Instant_Search::instance() ) {
432 return false;
433 }
434
435 if ( Helper::should_rerun_search_in_customizer_preview() ) {
436 Instant_Search::instance()->update_search_results_aggregations();
437 }
438
439 $filters = Instant_Search::instance()->get_filters();
440 if ( ! Helper::are_filters_by_widget_disabled() && ! $this->should_display_sitewide_filters() ) {
441 $filters = array_filter( $filters, array( $this, 'is_for_current_widget' ) );
442 }
443
444 $title = ! empty( $instance['title'] ) ? $instance['title'] : '';
445
446 /** This filter is documented in core/src/wp-includes/default-widgets.php */
447 $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
448
449 echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
450 ?>
451 <div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
452 <?php
453
454 if ( ! empty( $title ) ) {
455 /**
456 * Responsible for displaying the title of the Jetpack Search filters widget.
457 *
458 * @module search
459 *
460 * @param string $title The widget's title
461 * @param string $args['before_title'] The HTML tag to display before the title
462 * @param string $args['after_title'] The HTML tag to display after the title
463 *@since 5.7.0
464 */
465 do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
466 }
467
468 Template_Tags::render_widget_search_form( array(), '', '' );
469
470 if ( $filters ) {
471 /**
472 * Responsible for rendering filters to narrow down search results.
473 *
474 * @module search
475 *
476 * @param array $filters The possible filters for the current query.
477 * @param array $post_types An array of post types to limit filtering to.
478 *@since 5.8.0
479 */
480 do_action(
481 'jetpack_search_render_filters',
482 $filters,
483 null
484 );
485 }
486
487 echo '</div>';
488 echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
489 }
490
491 /**
492 * Render the instant widget for the overlay.
493 *
494 * @param array $args Widgets args supplied by the theme.
495 * @param array $instance The current widget instance.
496 * @since 8.3.0
497 */
498 public function widget_empty_instant( $args, $instance ) {
499 $title = $instance['title'] ?? '';
500
501 if ( empty( $title ) ) {
502 $title = '';
503 }
504
505 /** This filter is documented in core/src/wp-includes/default-widgets.php */
506 $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
507
508 echo $args['before_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
509 ?>
510 <div id="<?php echo esc_attr( $this->id ); ?>-wrapper" class="jetpack-instant-search-wrapper">
511 <?php
512
513 if ( ! empty( $title ) ) {
514 /**
515 * Responsible for displaying the title of the Jetpack Search filters widget.
516 *
517 * @module search
518 *
519 * @param string $title The widget's title
520 * @param string $args['before_title'] The HTML tag to display before the title
521 * @param string $args['after_title'] The HTML tag to display after the title
522 *@since 5.7.0
523 */
524 do_action( 'jetpack_search_render_filters_widget_title', $title, $args['before_title'], $args['after_title'] );
525 }
526
527 echo '</div>';
528 echo $args['after_widget']; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
529 }
530
531 /**
532 * Renders JavaScript for the sorting controls on the frontend.
533 *
534 * This JS is a bit complicated, but here's what it's trying to do:
535 * - find the search form
536 * - find the orderby/order fields and set default values
537 * - detect changes to the sort field, if it exists, and use it to set the order field values
538 *
539 * @param array $instance The current widget instance.
540 * @param string $order The order to initialize the select with.
541 * @param string $orderby The orderby to initialize the select with.
542 * @since 5.8.0
543 */
544 private function maybe_render_sort_javascript( $instance, $order, $orderby ) {
545 if ( Options::is_instant_enabled() ) {
546 return;
547 }
548
549 if ( ! empty( $instance['user_sort_enabled'] ) ) :
550 ?>
551 <script type="text/javascript">
552 var jetpackSearchModuleSorting = function() {
553 var orderByDefault = '<?php echo 'date' === $orderby ? 'date' : 'relevance'; ?>',
554 orderDefault = '<?php echo 'ASC' === $order ? 'ASC' : 'DESC'; ?>',
555 widgetId = decodeURIComponent( '<?php echo rawurlencode( $this->id ); ?>' ),
556 searchQuery = decodeURIComponent( '<?php echo rawurlencode( get_query_var( 's', '' ) ); ?>' ),
557 isSearch = <?php echo (int) is_search(); ?>;
558
559 var container = document.getElementById( widgetId + '-wrapper' ),
560 form = container.querySelector( '.jetpack-search-form form' ),
561 orderBy = form.querySelector( 'input[name=orderby]' ),
562 order = form.querySelector( 'input[name=order]' ),
563 searchInput = form.querySelector( 'input[name="s"]' ),
564 sortSelectInput = container.querySelector( '.jetpack-search-sort' );
565
566 orderBy.value = orderByDefault;
567 order.value = orderDefault;
568
569 // Some themes don't set the search query, which results in the query being lost
570 // when doing a sort selection. So, if the query isn't set, let's set it now. This approach
571 // is chosen over running a regex over HTML for every search query performed.
572 if ( isSearch && ! searchInput.value ) {
573 searchInput.value = searchQuery;
574 }
575
576 searchInput.classList.add( 'show-placeholder' );
577
578 sortSelectInput.addEventListener( 'change', function( event ) {
579 var values = event.target.value.split( '|' );
580 orderBy.value = values[0];
581 order.value = values[1];
582
583 form.submit();
584 } );
585 }
586
587 if ( document.readyState === 'interactive' || document.readyState === 'complete' ) {
588 jetpackSearchModuleSorting();
589 } else {
590 document.addEventListener( 'DOMContentLoaded', jetpackSearchModuleSorting );
591 }
592 </script>
593 <?php
594 endif;
595 }
596
597 /**
598 * Convert a sort string into the separate order by and order parts.
599 *
600 * @param string $sort A sort string.
601 *
602 * @return array Order by and order.
603 * @since 5.8.0
604 */
605 private function sorting_to_wp_query_param( $sort ) {
606 // phpcs:disable WordPress.Security.NonceVerification.Recommended
607 $parts = explode( '|', $sort );
608 $orderby = isset( $_GET['orderby'] ) && is_string( $_GET['orderby'] )
609 ? sanitize_sql_orderby( wp_unslash( $_GET['orderby'] ) )
610 : $parts[0];
611
612 $order = isset( $_GET['order'] ) && is_string( $_GET['order'] )
613 ? ( strtoupper( $_GET['order'] ) === 'ASC' ? 'ASC' : 'DESC' ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is validating.
614 : ( ( isset( $parts[1] ) && 'ASC' === strtoupper( $parts[1] ) ) ? 'ASC' : 'DESC' );
615
616 // phpcs:enable WordPress.Security.NonceVerification.Recommended
617
618 return array( $orderby, $order );
619 }
620
621 /**
622 * Updates a particular instance of the widget. Validates and sanitizes the options.
623 *
624 * @param array $new_instance New settings for this instance as input by the user via Search_Widget::form().
625 * @param array $old_instance Old settings for this instance.
626 *
627 * @return array Settings to save.
628 * @since 5.0.0
629 */
630 public function update( $new_instance, $old_instance ) {
631 $new_instance = $this->maybe_reformat_widget( $new_instance );
632 $instance = array();
633
634 $instance['title'] = sanitize_text_field( $new_instance['title'] );
635
636 // Keep `search_box_enabled` and `user_sort_enabled` settings when updating widget on Instant Search
637 // Set `search_box_enabled` and `user_sort_enabled` default to '1' when createing a NEW widget
638 if ( Options::is_instant_enabled() ) {
639 $instance['search_box_enabled'] = empty( $old_instance ) || empty( $old_instance['search_box_enabled'] ) ? '1' : $old_instance['search_box_enabled'];
640 $instance['user_sort_enabled'] = empty( $old_instance ) || empty( $old_instance['user_sort_enabled'] ) ? '1' : $old_instance['user_sort_enabled'];
641 } else {
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 }
645
646 $instance['sort'] = empty( $new_instance['sort'] ) ? self::DEFAULT_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 = (int) ( $new_instance['num_filters'][ $index ] ?? 1 );
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 'author':
675 $filters[] = array(
676 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ?? '' ),
677 'type' => 'author',
678 'count' => $count,
679 );
680 break;
681 case 'blog_id':
682 $filters[] = array(
683 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ?? '' ),
684 'type' => 'blog_id',
685 'count' => $count,
686 );
687 break;
688 case 'date_histogram':
689 $filters[] = array(
690 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ?? '' ),
691 'type' => 'date_histogram',
692 'count' => $count,
693 'field' => sanitize_key( $new_instance['date_histogram_field'][ $index ] ?? '' ),
694 'interval' => sanitize_key( $new_instance['date_histogram_interval'][ $index ] ?? '' ),
695 );
696 break;
697 case 'product_attribute':
698 $filter_data = array(
699 'name' => sanitize_text_field( $new_instance['filter_name'][ $index ] ?? '' ),
700 'type' => 'product_attribute',
701 'count' => $count,
702 );
703 // Save included attributes if any are selected.
704 if ( isset( $new_instance[ 'included_attributes_' . $index ] ) && is_array( $new_instance[ 'included_attributes_' . $index ] ) ) {
705 $filter_data['included_attributes'] = array_map( 'sanitize_key', $new_instance[ 'included_attributes_' . $index ] );
706 }
707 $filters[] = $filter_data;
708 break;
709 }
710 }
711 }
712
713 if ( ! empty( $filters ) ) {
714 $instance['filters'] = $filters;
715 }
716
717 return $instance;
718 }
719
720 /**
721 * Reformats the widget instance array to one that is recognized by the `update` function.
722 * This is only necessary when handling changes from the block-based widget editor.
723 *
724 * @param array $widget_instance - Jetpack Search widget instance.
725 *
726 * @return array - Potentially reformatted instance compatible with the save function.
727 */
728 protected function maybe_reformat_widget( $widget_instance ) {
729 if ( isset( $widget_instance['filter_type'] ) || ! isset( $widget_instance['filters'] ) || ! is_array( $widget_instance['filters'] ) ) {
730 return $widget_instance;
731 }
732
733 $instance = $widget_instance;
734 foreach ( $widget_instance['filters'] as $index => $filter ) {
735 $instance['filter_type'][] = $filter['type'] ?? '';
736 $instance['taxonomy_type'][] = $filter['taxonomy'] ?? '';
737 $instance['filter_name'][] = $filter['name'] ?? '';
738 $instance['num_filters'][] = $filter['count'] ?? 5;
739 $instance['date_histogram_field'][] = $filter['field'] ?? '';
740 $instance['date_histogram_interval'][] = $filter['interval'] ?? '';
741 // Handle included_attributes for product_attribute filters.
742 if ( isset( $filter['included_attributes'] ) && is_array( $filter['included_attributes'] ) ) {
743 $instance[ 'included_attributes_' . $index ] = $filter['included_attributes'];
744 }
745 }
746 unset( $instance['filters'] );
747 return $instance;
748 }
749
750 /**
751 * Outputs the settings update form.
752 *
753 * @param array $instance Previously saved values from database.
754 * @return string|void
755 * @since 5.0.0
756 */
757 public function form( $instance ) {
758 if ( Options::is_instant_enabled() ) {
759 return $this->form_for_instant_search( $instance );
760 }
761
762 $instance = $this->jetpack_search_populate_defaults( $instance );
763
764 $title = wp_strip_all_tags( $instance['title'] );
765
766 $hide_filters = Helper::are_filters_by_widget_disabled();
767
768 $classes = sprintf(
769 'jetpack-search-filters-widget %s %s %s',
770 $hide_filters ? 'hide-filters' : '',
771 $instance['search_box_enabled'] ? '' : 'hide-post-types',
772 $this->id
773 );
774 ?>
775 <div class="<?php echo esc_attr( $classes ); ?>">
776 <p>
777 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
778 <?php esc_html_e( 'Title (optional):', 'jetpack-search-pkg' ); ?>
779 </label>
780 <input
781 class="widefat"
782 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
783 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
784 type="text"
785 value="<?php echo esc_attr( $title ); ?>"
786 />
787 </p>
788
789 <p>
790 <label>
791 <input
792 type="checkbox"
793 class="jetpack-search-filters-widget__search-box-enabled"
794 name="<?php echo esc_attr( $this->get_field_name( 'search_box_enabled' ) ); ?>"
795 <?php checked( $instance['search_box_enabled'] ); ?>
796 />
797 <?php esc_html_e( 'Show search box', 'jetpack-search-pkg' ); ?>
798 </label>
799 </p>
800
801 <p>
802 <label>
803 <input
804 type="checkbox"
805 class="jetpack-search-filters-widget__sort-controls-enabled"
806 name="<?php echo esc_attr( $this->get_field_name( 'user_sort_enabled' ) ); ?>"
807 <?php checked( $instance['user_sort_enabled'] ); ?>
808 <?php disabled( ! $instance['search_box_enabled'] ); ?>
809 />
810 <?php esc_html_e( 'Show sort selection dropdown', 'jetpack-search-pkg' ); ?>
811 </label>
812 </p>
813
814 <p class="jetpack-search-filters-widget__post-types-select">
815 <label><?php esc_html_e( 'Post types to search (minimum of 1):', 'jetpack-search-pkg' ); ?></label>
816 <?php foreach ( get_post_types( array( 'exclude_from_search' => false ), 'objects' ) as $post_type ) : ?>
817 <label>
818 <input
819 type="checkbox"
820 value="<?php echo esc_attr( $post_type->name ); ?>"
821 name="<?php echo esc_attr( $this->get_field_name( 'post_types' ) ); ?>[]"
822 <?php checked( empty( $instance['post_types'] ) || in_array( $post_type->name, $instance['post_types'], true ) ); ?>
823 />&nbsp;
824 <?php echo esc_html( $post_type->label ); ?>
825 </label>
826 <?php endforeach; ?>
827 </p>
828
829 <p>
830 <label>
831 <?php esc_html_e( 'Default sort order:', 'jetpack-search-pkg' ); ?>
832 <select
833 name="<?php echo esc_attr( $this->get_field_name( 'sort' ) ); ?>"
834 class="widefat jetpack-search-filters-widget__sort-order">
835 <?php foreach ( $this->get_sort_types() as $sort_type => $label ) { ?>
836 <option value="<?php echo esc_attr( $sort_type ); ?>" <?php selected( $instance['sort'], $sort_type ); ?>>
837 <?php echo esc_html( $label ); ?>
838 </option>
839 <?php } ?>
840 </select>
841 </label>
842 </p>
843
844 <?php if ( ! $hide_filters ) : ?>
845 <script class="jetpack-search-filters-widget__filter-template" type="text/template">
846 <?php $this->render_widget_edit_filter( array(), true ); ?>
847 </script>
848 <div class="jetpack-search-filters-widget__filters">
849 <?php
850 $filter_index = 0;
851 foreach ( (array) $instance['filters'] as $filter ) :
852 $this->render_widget_edit_filter( $filter, false, false, $filter_index );
853 ++$filter_index;
854 endforeach;
855 ?>
856 </div>
857 <p class="jetpack-search-filters-widget__add-filter-wrapper">
858 <a class="button jetpack-search-filters-widget__add-filter" href="#">
859 <?php esc_html_e( 'Add a filter', 'jetpack-search-pkg' ); ?>
860 </a>
861 </p>
862 <noscript>
863 <p class="jetpack-search-filters-help">
864 <?php esc_html_e( 'Adding filters requires JavaScript!', 'jetpack-search-pkg' ); ?>
865 </p>
866 </noscript>
867 <?php if ( is_customize_preview() ) : ?>
868 <p class="jetpack-search-filters-help">
869 <a href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-search', array( 'anchor' => 'filters-not-showing-up' ) ) ); ?>" target="_blank">
870 <?php esc_html_e( "Why aren't my filters appearing?", 'jetpack-search-pkg' ); ?>
871 </a>
872 </p>
873 <?php endif; ?>
874 <?php endif; ?>
875 </div>
876 <?php
877 }
878
879 /**
880 * Outputs the widget update form to be used in the Customizer for Instant Search.
881 *
882 * @param array $instance Previously saved values from database.
883 * @since 8.6.0
884 */
885 private function form_for_instant_search( $instance ) {
886 $instance = $this->populate_defaults_for_instant_search( $instance );
887 $classes = sprintf( 'jetpack-search-filters-widget %s', $this->id );
888
889 ?>
890 <div class="<?php echo esc_attr( $classes ); ?>">
891 <!-- Title control -->
892 <p>
893 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>">
894 <?php esc_html_e( 'Title (optional):', 'jetpack-search-pkg' ); ?>
895 </label>
896 <input
897 class="widefat"
898 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"
899 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>"
900 type="text"
901 value="<?php echo esc_attr( wp_strip_all_tags( $instance['title'] ) ); ?>"
902 />
903 </p>
904
905 <!-- Filters control -->
906 <?php if ( ! Helper::are_filters_by_widget_disabled() ) : ?>
907 <div class="jetpack-search-filters-widget__filters">
908 <?php foreach ( (array) $instance['filters'] as $filter ) : ?>
909 <?php $this->render_widget_edit_filter( $filter, false, true ); ?>
910 <?php endforeach; ?>
911 </div>
912 <p class="jetpack-search-filters-widget__add-filter-wrapper">
913 <a class="button jetpack-search-filters-widget__add-filter" href="#">
914 <?php esc_html_e( 'Add a filter', 'jetpack-search-pkg' ); ?>
915 </a>
916 </p>
917 <script class="jetpack-search-filters-widget__filter-template" type="text/template">
918 <?php $this->render_widget_edit_filter( array(), true, true ); ?>
919 </script>
920 <noscript>
921 <p class="jetpack-search-filters-help">
922 <?php esc_html_e( 'Adding filters requires JavaScript!', 'jetpack-search-pkg' ); ?>
923 </p>
924 </noscript>
925 <?php endif; ?>
926 </div>
927 <?php
928 }
929
930 /**
931 * We need to render HTML in two formats: an Underscore template (client-side)
932 * and native PHP (server-side). This helper function allows for easy rendering
933 * of attributes in both formats.
934 *
935 * @param string $name Attribute name.
936 * @param string $value Attribute value.
937 * @param bool $is_template Whether this is for an Underscore template or not.
938 * @since 5.8.0
939 */
940 private function render_widget_attr( $name, $value, $is_template ) {
941 echo $is_template ? "<%= $name %>" : esc_attr( $value ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
942 }
943
944 /**
945 * We need to render HTML in two formats: an Underscore template (client-size)
946 * and native PHP (server-side). This helper function allows for easy rendering
947 * of the "selected" attribute in both formats.
948 *
949 * @param string $name Attribute name.
950 * @param string $value Attribute value.
951 * @param string $compare Value to compare to the attribute value to decide if it should be selected.
952 * @param bool $is_template Whether this is for an Underscore template or not.
953 * @since 5.8.0
954 */
955 private function render_widget_option_selected( $name, $value, $compare, $is_template ) {
956 $compare_js = rawurlencode( $compare );
957 echo $is_template ? "<%= decodeURIComponent( '$compare_js' ) === $name ? 'selected=\"selected\"' : '' %>" : selected( $value, $compare ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
958 }
959
960 /**
961 * Responsible for rendering a single filter in the customizer or the widget administration screen in wp-admin.
962 *
963 * We use this method for two purposes - rendering the fields server-side, and also rendering a script template for Underscore.
964 *
965 * @param array $filter The filter to render.
966 * @param bool $is_template Whether this is for an Underscore template or not.
967 * @param bool $is_instant_search Whether this site enables Instant Search or not.
968 * @param int $filter_index The index of this filter in the filters array.
969 * @since 5.7.0
970 */
971 public function render_widget_edit_filter( $filter, $is_template = false, $is_instant_search = false, $filter_index = 0 ) {
972 $args = wp_parse_args(
973 $filter,
974 array(
975 'name' => '',
976 'type' => 'taxonomy',
977 'taxonomy' => '',
978 'post_type' => '',
979 'field' => '',
980 'interval' => '',
981 'count' => self::DEFAULT_FILTER_COUNT,
982 )
983 );
984
985 $args['name_placeholder'] = Helper::generate_widget_filter_name( $args );
986
987 // Hide author & blog ID filters when Instant Search is turned off.
988 if ( ! $is_instant_search && in_array( $args['type'], array( 'author', 'blog_id' ), true ) ) :
989 return;
990 endif;
991 ?>
992 <div class="jetpack-search-filters-widget__filter is-<?php $this->render_widget_attr( 'type', $args['type'], $is_template ); ?>">
993 <p class="jetpack-search-filters-widget__type-select">
994 <label>
995 <?php esc_html_e( 'Filter Type:', 'jetpack-search-pkg' ); ?>
996 <select name="<?php echo esc_attr( $this->get_field_name( 'filter_type' ) ); ?>[]" class="widefat filter-select">
997 <option value="taxonomy" <?php $this->render_widget_option_selected( 'type', $args['type'], 'taxonomy', $is_template ); ?>>
998 <?php esc_html_e( 'Taxonomy', 'jetpack-search-pkg' ); ?>
999 </option>
1000 <option value="post_type" <?php $this->render_widget_option_selected( 'type', $args['type'], 'post_type', $is_template ); ?>>
1001 <?php esc_html_e( 'Post Type', 'jetpack-search-pkg' ); ?>
1002 </option>
1003 <?php if ( $is_instant_search ) : ?>
1004 <option value="author" <?php $this->render_widget_option_selected( 'type', $args['type'], 'author', $is_template ); ?>>
1005 <?php esc_html_e( 'Author', 'jetpack-search-pkg' ); ?>
1006 </option>
1007 <option value="blog_id" <?php $this->render_widget_option_selected( 'type', $args['type'], 'blog_id', $is_template ); ?>>
1008 <?php esc_html_e( 'Blog', 'jetpack-search-pkg' ); ?>
1009 </option>
1010 <?php endif; ?>
1011 <option value="date_histogram" <?php $this->render_widget_option_selected( 'type', $args['type'], 'date_histogram', $is_template ); ?>>
1012 <?php esc_html_e( 'Date', 'jetpack-search-pkg' ); ?>
1013 </option>
1014 <option value="product_attribute" <?php $this->render_widget_option_selected( 'type', $args['type'], 'product_attribute', $is_template ); ?>>
1015 <?php esc_html_e( 'Product Attributes', 'jetpack-search-pkg' ); ?>
1016 </option>
1017 </select>
1018 </label>
1019 </p>
1020
1021 <p class="jetpack-search-filters-widget__taxonomy-select">
1022 <label>
1023 <?php
1024 esc_html_e( 'Choose a taxonomy:', 'jetpack-search-pkg' );
1025 $seen_taxonomy_labels = array();
1026 ?>
1027 <select name="<?php echo esc_attr( $this->get_field_name( 'taxonomy_type' ) ); ?>[]" class="widefat taxonomy-select">
1028 <?php foreach ( $this->get_allowed_taxonomies_for_widget_filters() as $taxonomy ) : ?>
1029 <option value="<?php echo esc_attr( $taxonomy->name ); ?>" <?php $this->render_widget_option_selected( 'taxonomy', $args['taxonomy'], $taxonomy->name, $is_template ); ?>>
1030 <?php
1031 $label = in_array( $taxonomy->label, $seen_taxonomy_labels, true )
1032 ? sprintf(
1033 /* 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. */
1034 _x( '%1$s (%2$s)', 'A label for a taxonomy selector option', 'jetpack-search-pkg' ),
1035 $taxonomy->label,
1036 $taxonomy->name
1037 )
1038 : $taxonomy->label;
1039 echo esc_html( $label );
1040 $seen_taxonomy_labels[] = $taxonomy->label;
1041 ?>
1042 </option>
1043 <?php endforeach; ?>
1044 </select>
1045 </label>
1046 </p>
1047
1048 <p class="jetpack-search-filters-widget__date-histogram-select">
1049 <label>
1050 <?php esc_html_e( 'Choose a field:', 'jetpack-search-pkg' ); ?>
1051 <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_field' ) ); ?>[]" class="widefat date-field-select">
1052 <option value="post_date" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date', $is_template ); ?>>
1053 <?php esc_html_e( 'Date', 'jetpack-search-pkg' ); ?>
1054 </option>
1055 <option value="post_date_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_date_gmt', $is_template ); ?>>
1056 <?php esc_html_e( 'Date GMT', 'jetpack-search-pkg' ); ?>
1057 </option>
1058 <option value="post_modified" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified', $is_template ); ?>>
1059 <?php esc_html_e( 'Modified', 'jetpack-search-pkg' ); ?>
1060 </option>
1061 <option value="post_modified_gmt" <?php $this->render_widget_option_selected( 'field', $args['field'], 'post_modified_gmt', $is_template ); ?>>
1062 <?php esc_html_e( 'Modified GMT', 'jetpack-search-pkg' ); ?>
1063 </option>
1064 </select>
1065 </label>
1066 </p>
1067
1068 <p class="jetpack-search-filters-widget__date-histogram-select">
1069 <label>
1070 <?php esc_html_e( 'Choose an interval:', 'jetpack-search-pkg' ); ?>
1071 <select name="<?php echo esc_attr( $this->get_field_name( 'date_histogram_interval' ) ); ?>[]" class="widefat date-interval-select">
1072 <option value="month" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'month', $is_template ); ?>>
1073 <?php esc_html_e( 'Month', 'jetpack-search-pkg' ); ?>
1074 </option>
1075 <option value="year" <?php $this->render_widget_option_selected( 'interval', $args['interval'], 'year', $is_template ); ?>>
1076 <?php esc_html_e( 'Year', 'jetpack-search-pkg' ); ?>
1077 </option>
1078 </select>
1079 </label>
1080 </p>
1081
1082 <p class="jetpack-search-filters-widget__title">
1083 <label>
1084 <?php esc_html_e( 'Title:', 'jetpack-search-pkg' ); ?>
1085 <input
1086 class="widefat"
1087 type="text"
1088 name="<?php echo esc_attr( $this->get_field_name( 'filter_name' ) ); ?>[]"
1089 value="<?php $this->render_widget_attr( 'name', $args['name'], $is_template ); ?>"
1090 placeholder="<?php $this->render_widget_attr( 'name_placeholder', $args['name_placeholder'], $is_template ); ?>"
1091 />
1092 </label>
1093 </p>
1094
1095 <div class="jetpack-search-filters-widget__product-attribute-inclusions">
1096 <?php
1097 if ( function_exists( 'wc_get_attribute_taxonomies' ) && function_exists( 'wc_attribute_taxonomy_name' ) ) {
1098 $product_attributes = wc_get_attribute_taxonomies();
1099 $included_attributes = ! $is_template && isset( $args['included_attributes'] ) ? (array) $args['included_attributes'] : array();
1100
1101 if ( ! empty( $product_attributes ) ) :
1102 ?>
1103 <p>
1104 <label><?php esc_html_e( 'Select which attributes to display as filters. Leave blank to show all.', 'jetpack-search-pkg' ); ?></label>
1105 </p>
1106 <div class="jetpack-search-filters-widget__attribute-checkboxes">
1107 <?php
1108 foreach ( $product_attributes as $attribute ) :
1109 $attribute_name = wc_attribute_taxonomy_name( $attribute->attribute_name );
1110 $is_included = in_array( $attribute_name, $included_attributes, true );
1111 ?>
1112 <label class="jetpack-search-filters-widget__attribute-checkbox">
1113 <input
1114 type="checkbox"
1115 name="<?php echo esc_attr( $this->get_field_name( 'included_attributes_' . ( $is_template ? '<%= data.index %>' : $filter_index ) ) ); ?>[]"
1116 value="<?php echo esc_attr( $attribute_name ); ?>"
1117 <?php checked( $is_included ); ?>
1118 />
1119 <?php echo esc_html( $attribute->attribute_label ); ?>
1120 </label>
1121 <?php endforeach; ?>
1122 </div>
1123 <?php
1124 endif;
1125 }
1126 ?>
1127 </div>
1128
1129 <p class="jetpack-search-filters-widget__filter-count">
1130 <label>
1131 <?php esc_html_e( 'Maximum number of filters (1-50):', 'jetpack-search-pkg' ); ?>
1132 <input
1133 class="widefat filter-count"
1134 name="<?php echo esc_attr( $this->get_field_name( 'num_filters' ) ); ?>[]"
1135 type="number"
1136 value="<?php $this->render_widget_attr( 'count', $args['count'], $is_template ); ?>"
1137 min="1"
1138 max="50"
1139 step="1"
1140 required
1141 />
1142 </label>
1143 </p>
1144
1145 <p class="jetpack-search-filters-widget__controls">
1146 <a href="#" class="delete"><?php esc_html_e( 'Remove', 'jetpack-search-pkg' ); ?></a>
1147 </p>
1148 </div>
1149 <?php
1150 }
1151
1152 /**
1153 * Returns the taxonomies for search widget taxonomy dropdown.
1154 */
1155 protected function get_allowed_taxonomies_for_widget_filters() {
1156 return array_filter(
1157 get_taxonomies( array( 'public' => true ), 'objects' ),
1158 function ( $taxonomy ) {
1159 return in_array(
1160 $taxonomy->name,
1161 /**
1162 * Filters the taxonomies that shows in filter drop downs of the search widget.
1163 *
1164 * @since 0.16.0
1165 *
1166 * @param array $taxonomies_to_show List of taxonomies that shown for search widget.
1167 */
1168 apply_filters(
1169 'jetpack_search_allowed_taxonomies_for_widget_filters',
1170 array_merge( array( 'category', 'post_tag' ), Search_Sync_Module::get_all_taxonomies() )
1171 ),
1172 true
1173 );
1174 }
1175 );
1176 }
1177 }
1178