PluginProbe
ElasticPress / 4.2.2
ElasticPress v4.2.2
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Feature / InstantResults / InstantResults.php

InstantResults.php in ElasticPress 4.2.2, at includes/classes/Feature/InstantResults/InstantResults.php

869 lines 23.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Instant Search feature
4 *
5 * @package elasticpress
6 */
7
8 namespace ElasticPress\Feature\InstantResults;
9
10 use ElasticPress\Elasticsearch as Elasticsearch;
11 use ElasticPress\Feature as Feature;
12 use ElasticPress\FeatureRequirementsStatus;
13 use ElasticPress\Features as Features;
14 use ElasticPress\Indexables as Indexables;
15 use ElasticPress\Utils as Utils;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Instant Results feature class.
23 *
24 * @since 4.0.0
25 */
26 class InstantResults extends Feature {
27 /**
28 * Elasticsearch index name.
29 *
30 * @var string
31 */
32 protected $index;
33
34 /**
35 * Host URL.
36 *
37 * @var string
38 */
39 protected $host;
40
41 /**
42 * WooCommerce is in use.
43 *
44 * @var boolean
45 */
46 protected $is_woocommerce;
47
48 /**
49 * Elasticsearch query template.
50 *
51 * @var string
52 */
53 protected $search_template = '';
54
55 /**
56 * Feature settings
57 *
58 * @var array
59 */
60 protected $settings = [];
61
62 /**
63 * Initialize feature.
64 *
65 * @return void
66 */
67 public function __construct() {
68 $this->slug = 'instant-results';
69
70 $this->title = esc_html__( 'Instant Results', 'elasticpress' );
71
72 $this->summary = __( 'Search forms display results instantly after submission. A modal opens that populates results by querying ElasticPress directly.', 'elasticpress' );
73
74 $this->docs_url = __( 'https://elasticpress.zendesk.com/hc/en-us/articles/360050447492-Configuring-ElasticPress-via-the-Plugin-Dashboard#instant-results', 'elasticpress' );
75
76 $this->host = trailingslashit( Utils\get_host() );
77
78 $this->index = Indexables::factory()->get( 'post' )->get_index_name();
79
80 $this->is_woocommerce = function_exists( 'WC' );
81
82 $this->default_settings = [
83 'highlight_tag' => 'mark',
84 'facets' => 'post_type,category,post_tag',
85 'match_type' => 'all',
86 ];
87
88 $settings = $this->get_settings() ? $this->get_settings() : array();
89
90 $this->settings = wp_parse_args( $settings, $this->default_settings );
91
92 $this->requires_install_reindex = true;
93
94 $this->available_during_installation = true;
95
96 parent::__construct();
97 }
98
99 /**
100 * Output detailed feature description.
101 *
102 * @return void
103 */
104 public function output_feature_box_long() {
105 ?>
106 <p>
107 <?php
108 printf(
109 /* translators: %s: ElasticPress.io link. */
110 esc_html__( 'WordPress search forms will display results instantly. When the search query is submitted, a modal will open that populates results by querying ElasticPress directly, bypassing WordPress. As the user refines their search, results are refreshed. Requires an %s to function.', 'elasticpress' ),
111 sprintf(
112 '<a href=”%1$s” target="_blank">%2$s</a>',
113 'https://www.elasticpress.io/',
114 esc_html__( 'ElasticPress.io plan', 'elasticpress' )
115 )
116 );
117 ?>
118 </p>
119 <?php
120 }
121
122 /**
123 * Display feature settings.
124 *
125 * @return void
126 */
127 public function output_feature_box_settings() {
128 if ( ! $this->is_active() ) {
129 return;
130 }
131
132 $highlight_tags = array( 'mark', 'span', 'strong', 'em', 'i' );
133 ?>
134
135 <div class="field">
136 <label for="instant-results-highlight-tag" class="field-name status"><?php echo esc_html_e( 'Highlight tag ', 'elasticpress' ); ?></label>
137 <div class="input-wrap">
138 <select id="instant-results-highlight-tag" name="settings[highlight_tag]">
139 <option value=""><?php esc_html_e( 'None', 'elasticpress' ); ?></option>
140 <?php
141 foreach ( $highlight_tags as $highlight_tag ) {
142 printf(
143 '<option value="%1$s" %2$s>%3$s</option>',
144 esc_attr( $highlight_tag ),
145 selected( $this->settings['highlight_tag'], $highlight_tag, false ),
146 esc_html( $highlight_tag )
147 );
148 }
149 ?>
150 </select>
151 <p class="field-description"><?php esc_html_e( 'Highlight search terms in results with the selected HTML tag.', 'elasticpress' ); ?></p>
152 </div>
153 </div>
154 <div class="field">
155 <label for="feature_instant_results_facets" class="field-name status"><?php esc_html_e( 'Facets', 'elasticpress' ); ?></label>
156 <div class="input-wrap">
157 <input value="<?php echo esc_attr( $this->settings['facets'] ); ?>" type="text" name="settings[facets]" id="feature_instant_results_facets">
158 </div>
159 </div>
160 <div class="field">
161 <div class="field-name status"><?php esc_html_e( 'Match Type', 'elasticpress' ); ?></div>
162 <div class="input-wrap">
163 <label>
164 <input name="settings[match_type]" type="radio" <?php checked( $this->settings['match_type'], 'all' ); ?> value="all">
165 <?php echo wp_kses_post( __( 'Show any content tagged to <strong>all</strong> selected terms', 'elasticpress' ) ); ?>
166 </label><br>
167 <label>
168 <input name="settings[match_type]" type="radio" <?php checked( $this->settings['match_type'], 'any' ); ?> value="any">
169 <?php echo wp_kses_post( __( 'Show all content tagged to <strong>any</strong> selected term', 'elasticpress' ) ); ?>
170 </label>
171 <p class="field-description"><?php esc_html_e( '"All" will only show content that matches all facets. "Any" will show content that matches any facet.', 'elasticpress' ); ?></p>
172 </div>
173 </div>
174
175 <?php
176 }
177
178 /**
179 * Tell user whether requirements for feature are met or not.
180 *
181 * @return array $status Status array
182 */
183 public function requirements_status() {
184 $status = new FeatureRequirementsStatus( 2 );
185
186 $status->message = [];
187
188 if ( Utils\is_epio() ) {
189 $status->code = 1;
190
191 /**
192 * Whether the feature is available for non ElasticPress.io customers.
193 *
194 * Installations using self-hosted Elasticsearch will need to implement an API for
195 * handling search requests before making the feature available.
196 *
197 * @hook ep_instant_results_available
198 * @param string $available Whether the feature is available.
199 *
200 * @since 4.0.0
201 */
202 } elseif ( apply_filters( 'ep_instant_results_available', false ) ) {
203 $status->code = 1;
204 $status->message[] = esc_html__( 'You are using a custom proxy. Make sure you implement all security measures needed.', 'elasticpress' );
205 } else {
206 $status->message[] = wp_kses_post( __( "To use this feature you need to be an <a href='https://elasticpress.io'>ElasticPress.io</a> customer or implement a <a href='https://github.com/10up/elasticpress-proxy'>custom proxy</a>.", 'elasticpress' ) );
207 }
208
209 return $status;
210 }
211
212 /**
213 * Setup feature functionality.
214 *
215 * @return void
216 */
217 public function setup() {
218 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
219 add_filter( 'ep_after_update_feature', [ $this, 'epio_save_search_template' ] );
220 add_filter( 'ep_formatted_args', [ $this, 'maybe_apply_aggs_args' ], 10, 3 );
221 add_filter( 'ep_post_mapping', [ $this, 'add_mapping_properties' ] );
222 add_filter( 'ep_post_sync_args', [ $this, 'add_post_sync_args' ], 10, 2 );
223 add_filter( 'ep_after_sync_index', [ $this, 'epio_save_search_template' ] );
224 add_filter( 'ep_saved_weighting_configuration', [ $this, 'epio_save_search_template' ] );
225 add_action( 'pre_get_posts', [ $this, 'maybe_apply_product_visibility' ] );
226 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_frontend_assets' ] );
227 add_action( 'wp_footer', [ $this, 'render' ] );
228 }
229
230 /**
231 * Output modal markup.
232 */
233 public function render() {
234 echo '<div id="ep-instant-results"></div>';
235 }
236
237 /**
238 * Enqueue our autosuggest script.
239 */
240 public function enqueue_frontend_assets() {
241 if ( Utils\is_indexing() ) {
242 return;
243 }
244
245 wp_enqueue_style(
246 'elasticpress-instant-results',
247 EP_URL . 'dist/css/instant-results-styles.min.css',
248 Utils\get_asset_info( 'instant-results-styles', 'dependencies' ),
249 Utils\get_asset_info( 'instant-results-styles', 'version' )
250 );
251
252 wp_enqueue_script(
253 'elasticpress-instant-results',
254 EP_URL . 'dist/js/instant-results-script.min.js',
255 Utils\get_asset_info( 'instant-results-script', 'dependencies' ),
256 Utils\get_asset_info( 'instant-results-script', 'version' ),
257 true
258 );
259
260 /**
261 * The search API endpoint.
262 *
263 * @hook ep_instant_results_search_endpoint
264 * @param string $endpoint Endpoint path.
265 * @param string $index Elasticsearch index.
266 *
267 * @since 4.0.0
268 */
269 $api_endpoint = apply_filters( 'ep_instant_results_search_endpoint', "api/v1/search/posts/{$this->index}", $this->index );
270
271 wp_localize_script(
272 'elasticpress-instant-results',
273 'epInstantResults',
274 array(
275 'apiEndpoint' => $api_endpoint,
276 'apiHost' => ( 0 !== strpos( $api_endpoint, 'http' ) ) ? esc_url_raw( $this->host ) : '',
277 'argsSchema' => $this->get_args_schema(),
278 'currencyCode' => $this->is_woocommerce ? get_woocommerce_currency() : false,
279 'facets' => $this->get_facets_for_frontend(),
280 'highlightTag' => $this->settings['highlight_tag'],
281 'isWooCommerce' => $this->is_woocommerce,
282 'locale' => str_replace( '_', '-', get_locale() ),
283 'matchType' => $this->settings['match_type'],
284 'paramPrefix' => 'ep-',
285 'postTypeLabels' => $this->get_post_type_labels(),
286 )
287 );
288 }
289
290 /**
291 * Enqueue admin assets.
292 *
293 * @param string $hook_suffix The current admin page.
294 */
295 public function enqueue_admin_assets( $hook_suffix ) {
296 if ( 'toplevel_page_elasticpress' !== $hook_suffix ) {
297 return;
298 }
299
300 wp_enqueue_style( 'wp-edit-post' );
301
302 wp_enqueue_script(
303 'elasticpress-instant-results-admin',
304 EP_URL . 'dist/js/instant-results-admin-script.min.js',
305 Utils\get_asset_info( 'instant-results-admin-script', 'dependencies' ),
306 Utils\get_asset_info( 'instant-results-admin-script', 'version' ),
307 true
308 );
309
310 wp_localize_script(
311 'elasticpress-instant-results-admin',
312 'epInstantResultsAdmin',
313 array(
314 'facets' => $this->get_facets_for_admin(),
315 )
316 );
317 }
318
319 /**
320 * Save the search template to ElasticPress.io.
321 *
322 * @return void
323 */
324 public function epio_save_search_template() {
325 /**
326 * The search template API endpoint.
327 *
328 * @hook ep_instant_results_template_endpoint
329 * @param string $endpoint Endpoint path.
330 * @param string $index Elasticsearch index.
331 *
332 * @since 4.0.0
333 */
334 $endpoint = apply_filters( 'ep_instant_results_template_endpoint', "api/v1/search/posts/{$this->index}/template/", $this->index );
335
336 $search_template = $this->get_search_template();
337
338 Elasticsearch::factory()->remote_request(
339 $endpoint,
340 [
341 'blocking' => false,
342 'body' => $search_template,
343 'method' => 'PUT',
344 ]
345 );
346
347 /**
348 * Fires after the request is sent the search template API endpoint.
349 *
350 * @hook ep_instant_results_template_saved
351 * @param string $search_template The search template (JSON).
352 * @param string $index Index name.
353 *
354 * @since 4.0.0
355 */
356 do_action( 'ep_instant_results_template_saved', $search_template, $this->index );
357 }
358
359 /**
360 * Generate a search template.
361 *
362 * A search template is the JSON for an Elasticsearch query with a
363 * placeholder search term. The template is sent to ElasticPress.io where
364 * it's used to make Elasticsearch queries using search terms sent from
365 * the front end.
366 *
367 * @return string The search template as JSON.
368 */
369 public function get_search_template() {
370 $post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
371 $post_statuses = get_post_stati(
372 [
373 'public' => true,
374 'exclude_from_search' => false,
375 ]
376 );
377
378 /**
379 * The ID of the current user when generating the Instant Results
380 * search template.
381 *
382 * By default Instant Results sets the current user as anomnymous when
383 * generating the search template, so that any filters applied to
384 * queries for logged-in or specific users are not applied to the
385 * template. This filter supports setting a specific user as the
386 * current user while the template is generated.
387 *
388 * @hook ep_search_template_user_id
389 * @param {int} $user_id User ID to use.
390 * @return {int} New user ID to use.
391 * @since 4.1.0
392 */
393 $template_user_id = apply_filters( 'ep_search_template_user_id', 0 );
394 $original_user_id = get_current_user_id();
395
396 wp_set_current_user( $template_user_id );
397
398 add_filter( 'ep_intercept_remote_request', '__return_true' );
399 add_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10, 4 );
400 add_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10, 2 );
401
402 $query = new \WP_Query(
403 array(
404 'ep_integrate' => true,
405 'ep_search_template' => true,
406 'post_status' => array_values( $post_statuses ),
407 'post_type' => $post_types,
408 's' => '{{ep_placeholder}}',
409 )
410 );
411
412 remove_filter( 'ep_intercept_remote_request', '__return_true' );
413 remove_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10 );
414 remove_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10 );
415
416 wp_set_current_user( $original_user_id );
417
418 return $this->search_template;
419 }
420
421 /**
422 * Return true if a given feature is supported by Instant Results.
423 *
424 * Applied as a filter on Utils\is_integrated_request() so that features
425 * are enabled for the query that is used to generate the search template,
426 * regardless of the request type. This avoids the need to send a request
427 * to the front end.
428 *
429 * @param bool $is_integrated Whether queries for the request will be
430 * integrated.
431 * @param string $context Context for the original check. Usually the
432 * slug of the feature doing the check.
433 * @return bool True if the check is for a feature supported by instant
434 * search.
435 */
436 public function is_integrated_request( $is_integrated, $context ) {
437 $supported_contexts = [
438 'autosuggest',
439 'documents',
440 'search',
441 'weighting',
442 'woocommerce',
443 ];
444
445 return in_array( $context, $supported_contexts, true );
446 }
447
448 /**
449 * Store intercepted request body and return request result.
450 *
451 * @param object $response Response
452 * @param array $query Query
453 * @param array $args WP_Query argument array
454 * @param int $failures Count of failures in request loop
455 * @return object $response Response
456 */
457 public function intercept_search_request( $response, $query = [], $args = [], $failures = 0 ) {
458 $this->search_template = $query['args']['body'];
459
460 return wp_remote_request( $query['url'], $args );
461 }
462
463 /**
464 * Apply product visibility taxonomy query to search template queries.
465 *
466 * @param \WP_Query $query Query instance.
467 * @return void
468 */
469 public function maybe_apply_product_visibility( $query ) {
470 if ( true !== $query->get( 'ep_search_template' ) ) {
471 return;
472 }
473
474 if ( ! $this->is_woocommerce ) {
475 return;
476 }
477
478 $this->apply_product_visibility( $query );
479 }
480
481 /**
482 * Apply product visibility taxonomy query.
483 *
484 * Applies filters to exclude products set to be excluded from search. Out
485 * of stock products will also be excluded if WooCommerce is configured to
486 * hide those products.
487 *
488 * Mimics the logic of WC_Query::get_tax_query().
489 *
490 * @param \WP_Query $query Query instance.
491 * @return void
492 */
493 public function apply_product_visibility( $query ) {
494 $product_visibility_terms = wc_get_product_visibility_term_ids();
495 $product_visibility_not_in = (array) $product_visibility_terms['exclude-from-search'];
496
497 if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
498 $product_visibility_not_in[] = $product_visibility_terms['outofstock'];
499 }
500
501 if ( ! empty( $product_visibility_not_in ) ) {
502 $tax_query = $query->get( 'tax_query', array() );
503
504 $tax_query[] = array(
505 'taxonomy' => 'product_visibility',
506 'field' => 'term_taxonomy_id',
507 'terms' => $product_visibility_not_in,
508 'operator' => 'NOT IN',
509 );
510
511 $query->set( 'tax_query', $tax_query );
512 }
513 }
514
515 /**
516 * Apply aggregation args to search templates.
517 *
518 * @param array $formatted_args Formatted Elasticsearch query.
519 * @param array $query_vars Query variables
520 * @param \WP_Query $query Query instance.
521 * @return array Formatted Elasticsearch query.
522 */
523 public function maybe_apply_aggs_args( $formatted_args, $query_vars, $query ) {
524 if ( true !== $query->get( 'ep_search_template' ) ) {
525 return $formatted_args;
526 }
527
528 return $this->apply_aggs_args( $formatted_args );
529 }
530
531 /**
532 * Add aggregation args to Elasticsearch query for facets.
533 *
534 * @param array $formatted_args Formatted Elasticsearch query.
535 * @return array Formatted Elasticsearch query.
536 */
537 public function apply_aggs_args( $formatted_args ) {
538 $filter = $formatted_args['post_filter'];
539 $facets = $this->get_facets();
540
541 foreach ( $facets as $key => $facet ) {
542 $formatted_args['aggs'][ $key ]['aggs'] = $facet['aggs'];
543
544 if ( $filter ) {
545 $formatted_args['aggs'][ $key ]['filter'] = $filter;
546 }
547 }
548
549 return $formatted_args;
550 }
551
552 /**
553 * Add additional fields to post mapping.
554 *
555 * @param array $mapping Post mapping.
556 * @return array Post mapping.
557 */
558 public function add_mapping_properties( $mapping ) {
559 $elasticsearch_version = Elasticsearch::factory()->get_elasticsearch_version();
560
561 $properties = array(
562 'post_content_plain' => array( 'type' => 'text' ),
563 'price_html' => array( 'type' => 'text' ),
564 );
565
566 if ( version_compare( $elasticsearch_version, '7.0', '<' ) ) {
567 $mapping['mappings']['post']['properties'] = array_merge(
568 $mapping['mappings']['post']['properties'],
569 $properties
570 );
571 } else {
572 $mapping['mappings']['properties'] = array_merge(
573 $mapping['mappings']['properties'],
574 $properties
575 );
576 }
577
578 return $mapping;
579 }
580
581 /**
582 * Add data for additional mapping properties.
583 *
584 * @param array $post_args Post arguments.
585 * @param integer $post_id Post ID.
586 * @return array Post sync args.
587 */
588 public function add_post_sync_args( $post_args, $post_id ) {
589 $post = get_post( $post_id );
590
591 $post_args['post_content_plain'] = $this->prepare_plain_content_arg( $post );
592 $post_args['price_html'] = $this->prepare_price_html_arg( $post );
593
594 return $post_args;
595 }
596
597
598 /**
599 * Get data for the plain post content.
600 *
601 * @param WP_Post $post Post object.
602 * @return string Post content.
603 */
604 public function prepare_plain_content_arg( $post ) {
605 $post_content = apply_filters( 'the_content', $post->post_content );
606
607 return wp_strip_all_tags( $post_content );
608 }
609
610 /**
611 * Get data for the price HTML arg.
612 *
613 * @param WP_Post $post Post object.
614 * @return string|null Price HTML.
615 */
616 public function prepare_price_html_arg( $post ) {
617 if ( 'product' !== $post->post_type ) {
618 return null;
619 }
620
621 if ( ! $this->is_woocommerce ) {
622 return null;
623 }
624
625 $product = wc_get_product( $post );
626
627 return $product->get_price_html();
628 }
629
630 /**
631 * Get post type labels.
632 *
633 * Only the post type slug is indexed, so we'll need the labels on the
634 * front end for display.
635 *
636 * @return array Array of post types and their labels.
637 */
638 public function get_post_type_labels() {
639 $labels = [];
640
641 $post_types = Features::factory()->get_registered_feature( 'search' )->get_searchable_post_types();
642
643 foreach ( $post_types as $post_type ) {
644 $post_type_object = get_post_type_object( $post_type );
645 $post_type_labels = get_post_type_labels( $post_type_object );
646
647 $labels[ $post_type ] = array(
648 'plural' => $post_type_labels->name,
649 'singular' => $post_type_labels->singular_name,
650 );
651 }
652
653 return $labels;
654 }
655
656 /**
657 * Get available facets.
658 *
659 * @return array Available facets.
660 */
661 public function get_facets() {
662 $facets = [];
663
664 /**
665 * Post type facet.
666 */
667 $facets['post_type'] = array(
668 'type' => 'post_type',
669 'post_types' => [],
670 'labels' => array(
671 'admin' => __( 'Post type', 'elasticpress' ),
672 'frontend' => __( 'Type', 'elasticpress' ),
673 ),
674 'aggs' => array(
675 'post_type' => array(
676 'terms' => array(
677 'field' => 'post_type.raw',
678 ),
679 ),
680 ),
681 /**
682 * The post_type arg needs to be supported regardless of whether
683 * the Post Type facet is present to be able to support setting the
684 * post type from the search form.
685 *
686 * @see ElasticPress\Feature\InstantResults::get_args_schema()
687 */
688 'args' => array(),
689 );
690
691 /**
692 * Taxonomy facets.
693 */
694 $taxonomies = get_taxonomies( array( 'public' => true ), 'object' );
695 $taxonomies = apply_filters( 'ep_facet_include_taxonomies', $taxonomies );
696
697 foreach ( $taxonomies as $slug => $taxonomy ) {
698 $name = 'tax-' . $slug;
699 $labels = get_taxonomy_labels( $taxonomy );
700
701 $admin_label = sprintf(
702 /* translators: $1$s: Taxonomy name. %2$s: Taxonomy slug. */
703 esc_html__( '%1$s (%2$s)' ),
704 $labels->singular_name,
705 $slug
706 );
707
708 $facets[ $name ] = array(
709 'type' => 'taxonomy',
710 'post_types' => $taxonomy->object_type,
711 'labels' => array(
712 'admin' => $admin_label,
713 'frontend' => $labels->singular_name,
714 ),
715 'aggs' => array(
716 $name => array(
717 'terms' => array(
718 'field' => 'terms.' . $slug . '.facet',
719 'size' => apply_filters( 'ep_facet_taxonomies_size', 10000, $taxonomy ),
720 ),
721 ),
722 ),
723 'args' => array(
724 $name => array(
725 'type' => 'strings',
726 ),
727 ),
728 );
729 }
730
731 /**
732 * Price facet.
733 */
734 if ( $this->is_woocommerce ) {
735 $facets['price_range'] = array(
736 'type' => 'price_range',
737 'post_types' => [ 'product' ],
738 'labels' => array(
739 'admin' => __( 'Price range', 'elasticpress' ),
740 'frontend' => __( 'Price', 'elasticpress' ),
741 ),
742 'aggs' => array(
743 'max_price' => array(
744 'max' => array(
745 'field' => 'meta._price.double',
746 ),
747 ),
748 'min_price' => array(
749 'min' => array(
750 'field' => 'meta._price.double',
751 ),
752 ),
753 ),
754 'args' => array(
755 'max_price' => array(
756 'type' => 'number',
757 ),
758 'min_price' => array(
759 'type' => 'number',
760 ),
761 ),
762 );
763 }
764
765 return $facets;
766 }
767
768 /**
769 * Get facet configuration for the front end.
770 *
771 * @return Array Facet configuration for the front end.
772 */
773 public function get_facets_for_frontend() {
774 $selected_facets = explode( ',', $this->settings['facets'] );
775 $available_facets = $this->get_facets();
776
777 $facets = [];
778
779 foreach ( $selected_facets as $key ) {
780 if ( isset( $available_facets[ $key ] ) ) {
781 $facet = $available_facets[ $key ];
782
783 $facets[] = array(
784 'name' => $key,
785 'label' => $facet['labels']['frontend'],
786 'type' => $facet['type'],
787 'postTypes' => $facet['post_types'],
788 );
789 }
790 }
791
792 return $facets;
793 }
794
795 /**
796 * Get facet configuration for the admin.
797 *
798 * @return Array Facet configuration for the admin.
799 */
800 public function get_facets_for_admin() {
801 $available_facets = $this->get_facets();
802
803 $facets = [];
804
805 foreach ( $available_facets as $key => $facet ) {
806 $facets[ $key ] = array(
807 'label' => $facet['labels']['admin'],
808 );
809 }
810
811 return $facets;
812 }
813
814 /**
815 * Get schema for search args.
816 *
817 * @return array Search args schema.
818 */
819 public function get_args_schema() {
820 $args = array(
821 'highlight' => array(
822 'type' => 'string',
823 'default' => $this->settings['highlight_tag'],
824 'allowedValues' => [ $this->settings['highlight_tag'] ],
825 ),
826 'offset' => array(
827 'type' => 'number',
828 'default' => 0,
829 ),
830 'orderby' => array(
831 'type' => 'string',
832 'default' => 'relevance',
833 'allowedValues' => [ 'date', 'price', 'relevance' ],
834 ),
835 'order' => array(
836 'type' => 'string',
837 'default' => 'desc',
838 'allowedValues' => [ 'asc', 'desc' ],
839 ),
840 'per_page' => array(
841 'type' => 'number',
842 'default' => 6,
843 ),
844 'post_type' => array(
845 'type' => 'strings',
846 ),
847 'search' => array(
848 'type' => 'string',
849 'default' => '',
850 ),
851 'relation' => array(
852 'type' => 'string',
853 'default' => 'and',
854 'allowedValues' => [ 'and', 'or' ],
855 ),
856 );
857
858 $selected_facets = explode( ',', $this->settings['facets'] );
859 $available_facets = $this->get_facets();
860
861 foreach ( $selected_facets as $key ) {
862 if ( isset( $available_facets[ $key ] ) ) {
863 $args = array_merge( $args, $available_facets[ $key ]['args'] );
864 }
865 }
866 return $args;
867 }
868 }
869