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 / Facets / Renderer.php

Renderer.php in ElasticPress 4.2.2, at includes/classes/Feature/Facets/Renderer.php

466 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class responsible for rendering the filters.
4 *
5 * @since 4.2.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Feature\Facets;
10
11 use ElasticPress\Features as Features;
12 use ElasticPress\Utils as Utils;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Facets render class
20 */
21 class Renderer {
22 /**
23 * Output the widget or block HTML.
24 *
25 * @param array $args Widget args
26 * @param array $instance Instance settings
27 */
28 public function render( $args, $instance ) {
29 global $wp_query;
30
31 $args = wp_parse_args(
32 $args,
33 [
34 'before_widget' => '',
35 'before_title' => '',
36 'after_title' => '',
37 'after_widget' => '',
38 ]
39 );
40 $instance = wp_parse_args(
41 $instance,
42 [
43 'title' => '',
44 ]
45 );
46
47 $feature = Features::factory()->get_registered_feature( 'facets' );
48
49 if ( $wp_query->get( 'ep_facet', false ) && ! $feature->is_facetable( $wp_query ) ) {
50 return false;
51 }
52
53 $es_success = ( ! empty( $wp_query->elasticsearch_success ) ) ? true : false;
54
55 if ( ! $es_success ) {
56 return;
57 }
58
59 if ( empty( $instance['facet'] ) ) {
60 return;
61 }
62
63 $taxonomy = $instance['facet'];
64
65 if ( ! is_search() ) {
66
67 if ( is_tax() ) {
68 $post_type = get_taxonomy( get_queried_object()->taxonomy )->object_type;
69 } else {
70 $post_type = $wp_query->get( 'post_type' ) ? $wp_query->get( 'post_type' ) : 'post';
71 }
72
73 if ( ! is_object_in_taxonomy( $post_type, $taxonomy ) ) {
74 return;
75 }
76 }
77
78 $selected_filters = $feature->get_selected();
79
80 $match_type = ( ! empty( $instance['match_type'] ) ) ? $instance['match_type'] : 'all';
81
82 /**
83 * Get all the terms so we know if we should output the widget
84 */
85 $terms = get_terms(
86 /**
87 * Filter arguments passed to get_terms() while getting all possible terms for the facet widget.
88 *
89 * @since 3.5.0
90 * @hook ep_facet_search_get_terms_args
91 * @param {array} $query Weighting query
92 * @param {string} $post_type Post type
93 * @param {array} $args WP Query arguments
94 * @return {array} New query
95 */
96 apply_filters(
97 'ep_facet_search_get_terms_args',
98 [
99 'taxonomy' => $taxonomy,
100 'hide_empty' => true,
101 ],
102 $args,
103 $instance
104 )
105 );
106
107 /**
108 * Terms validity check
109 */
110 if ( is_wp_error( $terms ) || empty( $terms ) ) {
111 return;
112 }
113
114 $terms_by_slug = array();
115
116 foreach ( $terms as $term ) {
117 $terms_by_slug[ $term->slug ] = $term;
118
119 if ( ! empty( $GLOBALS['ep_facet_aggs'][ $taxonomy ][ $term->slug ] ) ) {
120 $term->count = $GLOBALS['ep_facet_aggs'][ $taxonomy ][ $term->slug ];
121 } else {
122 $term->count = 0;
123 }
124 }
125
126 /**
127 * Check to make sure all terms exist before proceeding
128 */
129 if ( ! empty( $selected_filters['taxonomies'][ $taxonomy ] ) && ! empty( $selected_filters['taxonomies'][ $taxonomy ]['terms'] ) ) {
130 foreach ( $selected_filters['taxonomies'][ $taxonomy ]['terms'] as $term_slug => $nothing ) {
131 if ( empty( $terms_by_slug[ $term_slug ] ) ) {
132 /**
133 * Term does not exist!
134 */
135 return;
136 }
137 }
138 }
139
140 $orderby = isset( $instance['orderby'] ) ? $instance['orderby'] : 'count';
141 $order = isset( $instance['order'] ) ? $instance['order'] : 'count';
142
143 $terms = Utils\get_term_tree( $terms, $orderby, $order, true );
144
145 $outputted_terms = array();
146
147 echo wp_kses_post( $args['before_widget'] );
148
149 if ( ! empty( $instance['title'] ) ) {
150 echo wp_kses_post( $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'] );
151 }
152
153 $taxonomy_object = get_taxonomy( $taxonomy );
154
155 /**
156 * Filter facet search threshold
157 *
158 * @hook ep_facet_search_threshold
159 * @param {int} $search_threshold Search threshold
160 * @param {string} $taxonomy Current taxonomy
161 * @return {int} New threshold
162 */
163 $search_threshold = apply_filters( 'ep_facet_search_threshold', 15, $taxonomy );
164 ?>
165
166 <div class="terms <?php if ( count( $terms_by_slug ) > $search_threshold ) : ?>searchable<?php endif; ?>">
167 <?php if ( count( $terms_by_slug ) > $search_threshold ) : ?>
168 <?php // translators: Taxonomy Name ?>
169 <input class="facet-search" type="search" placeholder="<?php printf( esc_html__( 'Search %s', 'elasticpress' ), esc_attr( $taxonomy_object->labels->name ) ); ?>">
170 <?php
171 endif;
172 ob_start();
173 ?>
174
175 <div class="inner">
176 <?php if ( ! empty( $selected_filters['taxonomies'][ $taxonomy ] ) ) : ?>
177 <?php
178 foreach ( $selected_filters['taxonomies'][ $taxonomy ]['terms'] as $term_slug => $value ) :
179 if ( ! empty( $outputted_terms[ $term_slug ] ) ) {
180 continue;
181 }
182
183 $term = $terms_by_slug[ $term_slug ];
184
185 if ( empty( $term->parent ) && empty( $term->children ) ) {
186 $outputted_terms[ $term_slug ] = $term;
187 $new_filters = $selected_filters;
188
189 if ( ! empty( $new_filters['taxonomies'][ $taxonomy ] ) && ! empty( $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term_slug ] ) ) {
190 unset( $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term_slug ] );
191 }
192
193 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
194 echo $this->get_facet_term_html(
195 $term,
196 $feature->build_query_url( $new_filters ),
197 true
198 );
199 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
200 } else {
201 /**
202 * This code is so that when we encounter a selected child/parent term, we push it's whole branch
203 * to the top. Very very painful.
204 */
205 $top_of_tree = $term;
206 $i = 0;
207
208 /**
209 * Get top of tree
210 */
211 while ( true && $i < 10 ) {
212 if ( ! empty( $term->parent_slug ) ) {
213 $top_of_tree = $terms_by_slug[ $term->parent_slug ];
214 } else {
215 break;
216 }
217
218 $i++;
219 }
220
221 $flat_ordered_terms = array();
222
223 $flat_ordered_terms[] = $top_of_tree;
224
225 $to_process = $this->order_by_selected( $top_of_tree->children, $selected_filters['taxonomies'][ $taxonomy ]['terms'], $order, $orderby );
226
227 while ( ! empty( $to_process ) ) {
228 $term = array_shift( $to_process );
229
230 $flat_ordered_terms[] = $term;
231
232 if ( ! empty( $term->children ) ) {
233 $to_process = array_merge( $this->order_by_selected( $term->children, $selected_filters['taxonomies'][ $taxonomy ]['terms'], $order, $orderby ), $to_process );
234 }
235 }
236
237 foreach ( $flat_ordered_terms as $term ) {
238 $selected = ! empty( $selected_filters['taxonomies'][ $taxonomy ]['terms'][ $term->slug ] );
239 $outputted_terms[ $term->slug ] = $term;
240 $new_filters = $selected_filters;
241
242 if ( $selected ) {
243 if ( ! empty( $new_filters['taxonomies'][ $taxonomy ] ) && ! empty( $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term->slug ] ) ) {
244 unset( $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term->slug ] );
245 }
246 } else {
247 if ( empty( $new_filters['taxonomies'][ $taxonomy ] ) ) {
248 $new_filters['taxonomies'][ $taxonomy ] = array(
249 'terms' => array(),
250 );
251 }
252
253 $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term->slug ] = true;
254 }
255
256 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
257 echo $this->get_facet_term_html(
258 $term,
259 $feature->build_query_url( $new_filters ),
260 $selected
261 );
262 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
263 }
264 }
265 endforeach;
266 ?>
267 <?php endif; ?>
268
269 <?php
270 foreach ( $terms as $term ) :
271 if ( ! empty( $outputted_terms[ $term->slug ] ) ) {
272 continue;
273 }
274
275 $new_filters = $selected_filters;
276
277 if ( empty( $new_filters['taxonomies'][ $taxonomy ] ) ) {
278 $new_filters['taxonomies'][ $taxonomy ] = array(
279 'terms' => array(),
280 );
281 }
282
283 $new_filters['taxonomies'][ $taxonomy ]['terms'][ $term->slug ] = true;
284
285 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
286 echo $this->get_facet_term_html(
287 $term,
288 $feature->build_query_url( $new_filters )
289 );
290 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
291 endforeach;
292 ?>
293 </div>
294 <?php $facet_html = ob_get_clean(); ?>
295
296 <?php
297 // phpcs:disable
298 /**
299 * Filter facet search widget HTML
300 *
301 * @hook ep_facet_search_widget
302 * @param {string} $facet_html Widget HTML
303 * @param {array} $selected_filters Selected filters
304 * @param {array} $terms_by_slug Terms by slug
305 * @param {array} $outputted_terms Outputted $terms
306 * @param {string} $title Widget title
307 * @return {string} New HTML
308 */
309 echo apply_filters( 'ep_facet_search_widget', $facet_html, $selected_filters, $terms_by_slug, $outputted_terms, $instance['title'] );
310 // phpcs:enable
311 ?>
312 </div>
313 <?php
314
315 // Enqueue Script & Styles
316 wp_enqueue_script( 'elasticpress-facets' );
317 wp_enqueue_style( 'elasticpress-facets' );
318
319 echo wp_kses_post( $args['after_widget'] );
320 }
321
322 /**
323 * Get the markup for an individual facet item.
324 *
325 * @param WP_Term $term Term object.
326 * @param string $url Filter URL.
327 * @param boolean $selected Whether the term is currently selected.
328 * @return string HTML for an individual facet term.
329 */
330 public function get_facet_term_html( $term, $url, $selected = false ) {
331 $href = sprintf(
332 'href="%s"',
333 esc_url( $url )
334 );
335
336 /**
337 * Filter the label for an individual facet term.
338 *
339 * @since 3.6.3
340 * @hook ep_facet_widget_term_label
341 * @param {string} $label Facet term label.
342 * @param {WP_Term} $term Term object.
343 * @param {boolean} $selected Whether the term is selected.
344 * @return {string} Individual facet term label.
345 */
346 $label = apply_filters( 'ep_facet_widget_term_label', $term->name, $term, $selected );
347
348 /**
349 * Filter the accessible label for an individual facet term link.
350 *
351 * Used as the aria-label attribute for filter links. The accessible
352 * label should include additional context around what action will be
353 * performed by visiting the link, such as whether the filter will be
354 * added or removed.
355 *
356 * @since 4.0.0
357 * @hook ep_facet_widget_term_accessible_label
358 * @param {string} $label Facet term accessible label.
359 * @param {WP_Term} $term Term object.
360 * @param {boolean} $selected Whether the term is selected.
361 * @return {string} Individual facet term accessible label.
362 */
363 $accessible_label = apply_filters(
364 'ep_facet_widget_term_accessible_label',
365 $selected
366 /* translators: %s: Filter term name. */
367 ? sprintf( __( 'Remove filter: %s', 'elasticpress' ), $term->name )
368 /* translators: %s: Filter term name. */
369 : sprintf( __( 'Apply filter: %s', 'elasticpress' ), $term->name ),
370 $term,
371 $selected
372 );
373
374 $link = sprintf(
375 '<a aria-label="%1$s" %2$s rel="nofollow"><div class="ep-checkbox %3$s" role="presentation"></div>%4$s</a>',
376 esc_attr( $accessible_label ),
377 $term->count ? $href : 'aria-role="link" aria-disabled="true"',
378 $selected ? 'checked' : '',
379 wp_kses_post( $label )
380 );
381
382 $html = sprintf(
383 '<div class="term level-%1$d %2$s %3$s" data-term-name="%4$s" data-term-slug="%5$s">%6$s</div>',
384 absint( $term->level ),
385 $selected ? 'selected' : '',
386 ! $term->count ? 'empty-term' : '',
387 esc_attr( strtolower( $term->name ) ),
388 esc_attr( strtolower( $term->slug ) ),
389 $link
390 );
391
392 /**
393 * Filter the HTML for an individual facet term.
394 *
395 * For term search to work correctly the outermost wrapper of the term
396 * HTML must have data-term-name and data-term-slug attributes set to
397 * lowercase versions of the term name and slug respectively.
398 *
399 * @since 3.6.3
400 * @hook ep_facet_widget_term_html
401 * @param {string} $html Facet term HTML.
402 * @param {WP_Term} $term Term object.
403 * @param {string} $url Filter URL.
404 * @param {boolean} $selected Whether the term is selected.
405 * @return {string} Individual facet term HTML.
406 */
407 return apply_filters( 'ep_facet_widget_term_html', $html, $term, $url, $selected );
408 }
409
410 /**
411 * Order terms putting selected at the top
412 *
413 * @param array $terms Array of terms
414 * @param array $selected_terms Selected terms
415 * @param string $order The order to sort from. Desc or Asc.
416 * @param string $orderby The orderby to sort items from.
417 * @return array
418 */
419 private function order_by_selected( $terms, $selected_terms, $order = false, $orderby = false ) {
420 $ordered_terms = [];
421 $terms_by_slug = [];
422
423 foreach ( $terms as $term ) {
424 $terms_by_slug[ $term->slug ] = $term;
425 }
426
427 foreach ( $selected_terms as $term_slug ) {
428 if ( ! empty( $terms_by_slug[ $term_slug ] ) ) {
429 $ordered_terms[ $term_slug ] = $terms_by_slug[ $term_slug ];
430 }
431 }
432
433 foreach ( $terms_by_slug as $term_slug => $term ) {
434 if ( empty( $ordered_terms[ $term_slug ] ) ) {
435 $ordered_terms[ $term_slug ] = $terms_by_slug[ $term_slug ];
436 }
437 }
438
439 if ( 'count' === $orderby ) {
440 if ( 'asc' === $order ) {
441 uasort(
442 $ordered_terms,
443 function( $a, $b ) {
444 return $a->count > $b->count;
445 }
446 );
447 } else {
448 uasort(
449 $ordered_terms,
450 function( $a, $b ) {
451 return $a->count < $b->count;
452 }
453 );
454 }
455 } else {
456 if ( 'asc' === $order ) {
457 ksort( $ordered_terms );
458 } else {
459 krsort( $ordered_terms );
460 }
461 }
462
463 return array_values( $ordered_terms );
464 }
465 }
466