PluginProbe
ElasticPress / 4.2.0
ElasticPress v4.2.0
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.0, at includes/classes/Feature/Facets/Renderer.php

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