PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
← All changes | includes/widgets/Facet_Taxonomy/Facet_Taxonomy.php +348 -68 51.1.78 → 51.1.87 View file →
@@ -35,12 +35,22 @@
35 35 * @return string
36 36 */
37 37 public function get_title(): string
38 38 {
39 - return esc_html__('Facet Taxonomy', 'king-addons');
39 + return esc_html__('Taxonomy Filter', 'king-addons');
40 40 }
41 41
42 42 /**
43 + * Keywords.
44 + *
45 + * @return array<int, string>
46 + */
47 + public function get_keywords(): array
48 + {
49 + return ['shop', 'product', 'filter', 'filters', 'ajax', 'woocommerce', 'category', 'attribute', 'faceted', 'smart filters'];
50 + }
51 +
52 + /**
43 53 * Widget icon.
44 54 *
45 55 * @return string
46 56 */
@@ -45,9 +55,9 @@
45 55 * @return string
46 56 */
47 57 public function get_icon(): string
48 58 {
49 - return 'eicon-checkbox';
59 + return 'king-addons-icon king-addons-facet-taxonomy';
50 60 }
51 61
52 62 /**
53 63 * Categories.
@@ -97,9 +107,9 @@
97 107 {
98 108 $this->start_controls_section(
99 109 'kng_facet_section',
100 110 [
101 - 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Facet Taxonomy', 'king-addons'),
111 + 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Taxonomy Filter', 'king-addons'),
102 112 'tab' => Controls_Manager::TAB_CONTENT,
103 113 ]
104 114 );
105 115
@@ -105,11 +115,12 @@
105 115
106 116 $this->add_control(
107 117 'kng_filters_query_id',
108 118 [
109 - 'label' => esc_html__('Filter Query ID', 'king-addons'),
119 + 'label' => esc_html__('Shop Filters ID', 'king-addons'),
110 120 'type' => Controls_Manager::TEXT,
111 121 'placeholder' => esc_html__('shop_grid_1', 'king-addons'),
122 + 'description' => esc_html__('Must match the Shop Filters ID on the product grid.', 'king-addons'),
112 123 ]
113 124 );
114 125
115 126 $this->add_control(
@@ -140,9 +151,9 @@
140 151 [
141 152 'label' => esc_html__('Select Terms', 'king-addons'),
142 153 'type' => Controls_Manager::SELECT2,
143 154 'multiple' => true,
144 - 'options' => $this->get_terms_options('product_cat'),
155 + 'options' => $this->get_editor_term_options(),
145 156 'condition' => [
146 157 'kng_terms_mode' => 'auto',
147 158 ],
148 159 ]
@@ -183,8 +194,21 @@
183 194 ]
184 195 );
185 196
186 197 $this->add_control(
198 + 'kng_hierarchy',
199 + [
200 + 'label' => sprintf(__('Show child terms %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
201 + 'type' => Controls_Manager::SWITCHER,
202 + 'return_value' => 'yes',
203 + 'default' => '',
204 + 'description' => esc_html__('Indent child categories under their parent. Applies to hierarchical taxonomies such as product categories.', 'king-addons'),
205 + ]
206 + );
207 +
208 + Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'facet-taxonomy', 'kng_hierarchy', 'yes');
209 +
210 + $this->add_control(
187 211 'kng_display_mode',
188 212 [
189 213 'label' => esc_html__('Display Mode', 'king-addons'),
190 214 'type' => Controls_Manager::SELECT,
@@ -189,18 +213,20 @@
189 213 'label' => esc_html__('Display Mode', 'king-addons'),
190 214 'type' => Controls_Manager::SELECT,
191 215 'options' => [
192 216 'text' => esc_html__('Text', 'king-addons'),
193 - 'swatch' => esc_html__('Swatch', 'king-addons'),
217 + 'swatch' => sprintf(__('Swatch %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
194 218 ],
195 219 'default' => 'text',
196 220 ]
197 221 );
198 222
223 + Core::renderUpgradeProNotice($this, Controls_Manager::RAW_HTML, 'facet-taxonomy', 'kng_display_mode', 'swatch');
224 +
199 225 $this->add_control(
200 226 'kng_swatch_type',
201 227 [
202 - 'label' => esc_html__('Swatch Type', 'king-addons'),
228 + 'label' => sprintf(__('Swatch Type %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
203 229 'type' => Controls_Manager::SELECT,
204 230 'options' => [
205 231 'color' => esc_html__('Color', 'king-addons'),
206 232 'image' => esc_html__('Image URL', 'king-addons'),
@@ -215,12 +241,12 @@
215 241
216 242 $this->add_control(
217 243 'kng_swatch_map',
218 244 [
219 - 'label' => esc_html__('Swatch Map', 'king-addons'),
245 + 'label' => sprintf(__('Swatch Map %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
220 246 'type' => Controls_Manager::TEXTAREA,
221 247 'placeholder' => "red:#ff0000\nblue:#0000ff\npattern:https://example.com/pattern.jpg",
222 - 'description' => esc_html__('Key-value per line: slug:value (hex color or image URL).', 'king-addons'),
248 + 'description' => esc_html__('Optional. A line here overrides the color or image saved on the WooCommerce attribute.', 'king-addons'),
223 249 'condition' => [
224 250 'kng_display_mode' => 'swatch',
225 251 ],
226 252 ]
@@ -226,8 +252,11 @@
226 252 ]
227 253 );
228 254
229 255 $this->end_controls_section();
256 +
257 + require_once KING_ADDONS_PATH . 'includes/helpers/Faceted/Style_Controls.php';
258 + Facet_Style_Controls::list($this);
230 259 }
231 260
232 261 /**
233 262 * Render widget output.
@@ -238,80 +267,301 @@
238 267 {
239 268 $settings = $this->get_settings_for_display();
240 269 $query_id = sanitize_title($settings['kng_filters_query_id'] ?? '');
241 270 $taxonomy = sanitize_key($settings['kng_taxonomy'] ?? '');
242 - $terms = [];
271 + if ('' === $query_id || '' === $taxonomy) {
272 + return;
273 + }
243 274
244 - if (($settings['kng_terms_mode'] ?? 'auto') === 'auto') {
245 - $selected = $settings['kng_terms_select'] ?? [];
246 - if (!empty($selected) && is_array($selected)) {
247 - $terms = array_map('sanitize_title', $selected);
248 - } else {
249 - $terms = $this->get_term_slugs_for_tax($taxonomy);
275 + $terms = $this->resolve_terms($taxonomy, $settings);
276 + if (empty($terms)) {
277 + return;
278 + }
279 +
280 + $can_pro = function_exists('king_addons_can_use_pro') && king_addons_can_use_pro();
281 + $hierarchical = $can_pro
282 + && ($settings['kng_hierarchy'] ?? '') === 'yes'
283 + && is_taxonomy_hierarchical($taxonomy);
284 + if ($hierarchical) {
285 + $terms = $this->with_ancestors($terms, $taxonomy);
286 + }
287 +
288 + $can_swatch = $can_pro;
289 + $swatch_map = $can_swatch ? $this->parse_swatch_map($settings['kng_swatch_map'] ?? '') : [];
290 + $is_swatch = $can_swatch && ($settings['kng_display_mode'] ?? 'text') === 'swatch';
291 + $swatch_type = $settings['kng_swatch_type'] ?? 'color';
292 + $taxonomy_obj = get_taxonomy($taxonomy);
293 + $taxonomy_label = ($taxonomy_obj && !empty($taxonomy_obj->labels->singular_name))
294 + ? (string) $taxonomy_obj->labels->singular_name
295 + : $taxonomy;
296 +
297 + echo '<div class="king-addons-facet king-addons-facet--taxonomy">';
298 + $this->render_term_branch(
299 + $terms,
300 + $hierarchical ? 0 : -1,
301 + $hierarchical,
302 + $query_id,
303 + $taxonomy,
304 + $taxonomy_label,
305 + $settings,
306 + $is_swatch,
307 + $swatch_type,
308 + $swatch_map
309 + );
310 + echo '</div>';
311 + }
312 +
313 + /**
314 + * Terms chosen for this filter.
315 + *
316 + * @param string $taxonomy Taxonomy name.
317 + * @param array<string, mixed> $settings Widget settings.
318 + * @return array<int, \WP_Term>
319 + */
320 + private function resolve_terms(string $taxonomy, array $settings): array
321 + {
322 + if (($settings['kng_terms_mode'] ?? 'auto') === 'manual') {
323 + $terms_raw = $settings['kng_terms'] ?? '';
324 + $slugs = array_filter(array_map('sanitize_title', preg_split("/\r\n|\n|\r/", (string) $terms_raw)));
325 + $terms = [];
326 + foreach ($slugs as $slug) {
327 + $term = get_term_by('slug', $slug, $taxonomy);
328 + if ($term instanceof \WP_Term) {
329 + $terms[] = $term;
330 + }
250 331 }
251 - } else {
252 - $terms_raw = $settings['kng_terms'] ?? '';
253 - $terms = array_filter(array_map('trim', preg_split("/\r\n|\n|\r/", (string) $terms_raw)));
332 + return $terms;
254 333 }
255 334
256 - if (empty($terms)) {
335 + $selected = $settings['kng_terms_select'] ?? [];
336 + $args = [
337 + 'taxonomy' => $taxonomy,
338 + 'hide_empty' => false,
339 + 'number' => 50,
340 + 'orderby' => 'name',
341 + 'order' => 'ASC',
342 + ];
343 + if (!empty($selected) && is_array($selected)) {
344 + $args['slug'] = array_map('sanitize_title', $selected);
345 + unset($args['number']);
346 + }
347 +
348 + $terms = get_terms($args);
349 + return is_array($terms) ? $terms : [];
350 + }
351 +
352 + /**
353 + * Include missing parents so a child is not rendered as a root term.
354 + *
355 + * @param array<int, \WP_Term> $terms Terms already selected.
356 + * @param string $taxonomy Taxonomy name.
357 + * @return array<int, \WP_Term>
358 + */
359 + private function with_ancestors(array $terms, string $taxonomy): array
360 + {
361 + $by_id = [];
362 + foreach ($terms as $term) {
363 + $by_id[$term->term_id] = $term;
364 + $parent_id = (int) $term->parent;
365 + while ($parent_id > 0 && !isset($by_id[$parent_id])) {
366 + $parent = get_term($parent_id, $taxonomy);
367 + if (!$parent instanceof \WP_Term) {
368 + break;
369 + }
370 + $by_id[$parent->term_id] = $parent;
371 + $parent_id = (int) $parent->parent;
372 + }
373 + }
374 +
375 + return array_values($by_id);
376 + }
377 +
378 + /**
379 + * Render one level of the term list. Parent -1 means a flat list.
380 + *
381 + * @param array<int, \WP_Term> $terms All terms.
382 + * @param int $parent Parent term id, or -1 for a flat list.
383 + * @param bool $hierarchical Whether to nest children.
384 + * @param string $query_id Shop Filters ID.
385 + * @param string $taxonomy Taxonomy name.
386 + * @param string $taxonomy_label Singular taxonomy label.
387 + * @param array<string, mixed> $settings Widget settings.
388 + * @param bool $is_swatch Swatch display.
389 + * @param string $swatch_type color|image|text.
390 + * @param array<string, string> $swatch_map Optional overrides.
391 + * @return void
392 + */
393 + private function render_term_branch(
394 + array $terms,
395 + int $parent,
396 + bool $hierarchical,
397 + string $query_id,
398 + string $taxonomy,
399 + string $taxonomy_label,
400 + array $settings,
401 + bool $is_swatch,
402 + string $swatch_type,
403 + array $swatch_map
404 + ): void {
405 + $level = [];
406 + foreach ($terms as $term) {
407 + if ($parent === -1 || (int) $term->parent === $parent) {
408 + $level[] = $term;
409 + }
410 + }
411 + if (empty($level)) {
257 412 return;
258 413 }
259 414
260 - $swatch_map = $this->parse_swatch_map($settings['kng_swatch_map'] ?? '');
261 - $is_swatch = ($settings['kng_display_mode'] ?? 'text') === 'swatch';
262 - $swatch_type = $settings['kng_swatch_type'] ?? 'color';
415 + $list_class = 'king-addons-facet__list';
416 + if ($parent > 0) {
417 + $list_class .= ' king-addons-facet__list--child';
418 + }
263 419
420 + echo '<ul class="' . esc_attr($list_class) . '" data-ka-filters-query-id="' . esc_attr($query_id) . '">';
421 + foreach ($level as $term) {
422 + $this->render_term_item(
423 + $term,
424 + $query_id,
425 + $taxonomy,
426 + $taxonomy_label,
427 + $settings,
428 + $is_swatch,
429 + $swatch_type,
430 + $swatch_map
431 + );
432 + if ($hierarchical) {
433 + $this->render_term_branch(
434 + $terms,
435 + (int) $term->term_id,
436 + true,
437 + $query_id,
438 + $taxonomy,
439 + $taxonomy_label,
440 + $settings,
441 + $is_swatch,
442 + $swatch_type,
443 + $swatch_map
444 + );
445 + }
446 + echo '</li>';
447 + }
448 + echo '</ul>';
449 + }
450 +
451 + /**
452 + * One checkbox or swatch row.
453 + *
454 + * @param \WP_Term $term Term.
455 + * @param string $query_id Shop Filters ID.
456 + * @param string $taxonomy Taxonomy name.
457 + * @param string $taxonomy_label Singular label.
458 + * @param array<string, mixed> $settings Widget settings.
459 + * @param bool $is_swatch Swatch display.
460 + * @param string $swatch_type color|image|text.
461 + * @param array<string, string> $swatch_map Optional overrides.
462 + * @return void
463 + */
464 + private function render_term_item(
465 + \WP_Term $term,
466 + string $query_id,
467 + string $taxonomy,
468 + string $taxonomy_label,
469 + array $settings,
470 + bool $is_swatch,
471 + string $swatch_type,
472 + array $swatch_map
473 + ): void {
474 + $slug = $term->slug;
475 + $term_label = $term->name;
476 + $swatch = $is_swatch ? $this->resolve_swatch($term, $swatch_type, $swatch_map) : ['type' => '', 'value' => ''];
264 477 ?>
265 - <div class="king-addons-facet king-addons-facet--taxonomy">
266 - <ul class="king-addons-facet__list" data-ka-filters-query-id="<?php echo esc_attr($query_id); ?>">
267 - <?php foreach ($terms as $term) : ?>
268 - <li class="king-addons-facet__item">
269 - <label class="king-addons-facet__label">
270 - <input
271 - type="checkbox"
272 - class="king-addons-facet__input"
273 - data-ka-filter-type="taxonomy"
274 - data-ka-filters-query-id="<?php echo esc_attr($query_id); ?>"
275 - data-ka-taxonomy="<?php echo esc_attr($taxonomy); ?>"
276 - data-ka-term="<?php echo esc_attr($term); ?>"
277 - data-ka-show-counts="<?php echo esc_attr(($settings['kng_show_counts'] ?? '') === 'yes' ? '1' : '0'); ?>"
278 - data-ka-swatch="<?php echo $is_swatch ? '1' : '0'; ?>"
279 - data-ka-count-key="<?php echo esc_attr($taxonomy . ':' . $term); ?>"
280 - />
281 - <?php if ($is_swatch) : ?>
282 - <?php
283 - $value = $swatch_map[$term] ?? '';
284 - $style_attr = '';
285 - $class = 'king-addons-facet__swatch';
286 - if ('color' === $swatch_type && $value) {
287 - $style_attr = ' style="background-color:' . esc_attr($value) . ';"';
288 - } elseif ('image' === $swatch_type && $value) {
289 - $style_attr = ' style="background-image:url(' . esc_url($value) . ');"';
290 - $class .= ' king-addons-facet__swatch--image';
291 - }
292 - ?>
293 - <span class="<?php echo esc_attr($class); ?>"<?php echo $style_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>></span>
294 - <span class="king-addons-facet__text"><?php echo esc_html($term); ?></span>
295 - <?php else : ?>
296 - <span class="king-addons-facet__text"><?php echo esc_html($term); ?></span>
297 - <?php endif; ?>
298 - <?php if (($settings['kng_show_counts'] ?? '') === 'yes') : ?>
299 - <span
300 - class="king-addons-facet__count"
301 - data-ka-count="<?php echo esc_attr($taxonomy . ':' . $term); ?>"
302 - data-ka-hide-zero="<?php echo esc_attr(($settings['kng_hide_zero'] ?? '') === 'yes' ? '1' : '0'); ?>"
303 - ></span>
304 - <?php endif; ?>
305 - </label>
306 - </li>
307 - <?php endforeach; ?>
308 - </ul>
309 - </div>
478 + <li class="king-addons-facet__item<?php echo (int) $term->parent > 0 ? ' king-addons-facet__item--child' : ''; ?>">
479 + <label class="king-addons-facet__label">
480 + <input
481 + type="checkbox"
482 + class="king-addons-facet__input"
483 + data-ka-filter-type="taxonomy"
484 + data-ka-filters-query-id="<?php echo esc_attr($query_id); ?>"
485 + data-ka-taxonomy="<?php echo esc_attr($taxonomy); ?>"
486 + data-ka-taxonomy-label="<?php echo esc_attr($taxonomy_label); ?>"
487 + data-ka-term="<?php echo esc_attr($slug); ?>"
488 + data-ka-term-label="<?php echo esc_attr($term_label); ?>"
489 + data-ka-show-counts="<?php echo esc_attr(($settings['kng_show_counts'] ?? '') === 'yes' ? '1' : '0'); ?>"
490 + data-ka-swatch="<?php echo $is_swatch ? '1' : '0'; ?>"
491 + data-ka-count-key="<?php echo esc_attr($taxonomy . ':' . $slug); ?>"
492 + />
493 + <?php if ($is_swatch) : ?>
494 + <?php
495 + $class = 'king-addons-facet__swatch';
496 + $style_attr = '';
497 + if ('image' === $swatch['type'] && $swatch['value'] !== '') {
498 + $style_attr = ' style="background-image:url(' . esc_url($swatch['value']) . ');"';
499 + $class .= ' king-addons-facet__swatch--image';
500 + } elseif ('color' === $swatch['type'] && $swatch['value'] !== '') {
501 + $style_attr = ' style="background-color:' . esc_attr($swatch['value']) . ';"';
502 + } else {
503 + $class .= ' king-addons-facet__swatch--letter';
504 + }
505 + ?>
506 + <span class="<?php echo esc_attr($class); ?>"<?php echo $style_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo 'letter' === $swatch['type'] || $swatch['value'] === '' ? esc_html(mb_substr($term_label, 0, 1)) : ''; ?></span>
507 + <span class="king-addons-facet__text"><?php echo esc_html($term_label); ?></span>
508 + <?php else : ?>
509 + <span class="king-addons-facet__box" aria-hidden="true"></span>
510 + <span class="king-addons-facet__text"><?php echo esc_html($term_label); ?></span>
511 + <?php endif; ?>
512 + <?php if (($settings['kng_show_counts'] ?? '') === 'yes') : ?>
513 + <span
514 + class="king-addons-facet__count"
515 + data-ka-count="<?php echo esc_attr($taxonomy . ':' . $slug); ?>"
516 + data-ka-hide-zero="<?php echo esc_attr(($settings['kng_hide_zero'] ?? '') === 'yes' ? '1' : '0'); ?>"
517 + ></span>
518 + <?php endif; ?>
519 + </label>
310 520 <?php
311 521 }
312 522
313 523 /**
524 + * Color or image for a swatch. The textarea overrides attribute data.
525 + *
526 + * @param \WP_Term $term Term.
527 + * @param string $type color|image|text.
528 + * @param array<string, string> $map Optional slug overrides.
529 + * @return array{type: string, value: string}
530 + */
531 + private function resolve_swatch(\WP_Term $term, string $type, array $map): array
532 + {
533 + $override = $map[$term->slug] ?? '';
534 + if ($override !== '') {
535 + $is_image = (bool) preg_match('#^https?://#i', $override);
536 + return [
537 + 'type' => $is_image ? 'image' : 'color',
538 + 'value' => $is_image ? $override : (sanitize_hex_color($override) ?: $override),
539 + ];
540 + }
541 +
542 + if ('image' === $type) {
543 + $thumb_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
544 + if ($thumb_id > 0) {
545 + $url = wp_get_attachment_image_url($thumb_id, 'thumbnail');
546 + if (is_string($url) && $url !== '') {
547 + return ['type' => 'image', 'value' => $url];
548 + }
549 + }
550 + }
551 +
552 + $color_raw = get_term_meta($term->term_id, 'color', true);
553 + if (is_string($color_raw) && $color_raw !== '') {
554 + $hex = sanitize_hex_color($color_raw);
555 + if ($hex) {
556 + return ['type' => 'color', 'value' => $hex];
557 + }
558 + }
559 +
560 + return ['type' => 'letter', 'value' => ''];
561 + }
562 +
563 + /**
314 564 * Get available taxonomies list.
315 565 *
316 566 * @return array<string,string>
317 567 */
@@ -345,8 +595,38 @@
345 595
346 596 if (is_array($terms)) {
347 597 foreach ($terms as $term) {
348 598 $options[$term->slug] = esc_html($term->name);
599 + }
600 + }
601 +
602 + return $options;
603 + }
604 +
605 + /**
606 + * Term options for the editor select (product taxonomies + attributes).
607 + *
608 + * @return array<string,string>
609 + */
610 + private function get_editor_term_options(): array
611 + {
612 + $options = [];
613 + $taxonomies = ['product_cat', 'product_tag'];
614 +
615 + if (function_exists('wc_get_attribute_taxonomies')) {
616 + foreach (wc_get_attribute_taxonomies() as $attribute) {
617 + if (empty($attribute->attribute_name) || !function_exists('wc_attribute_taxonomy_name')) {
618 + continue;
619 + }
620 + $taxonomies[] = wc_attribute_taxonomy_name($attribute->attribute_name);
621 + }
622 + }
623 +
624 + foreach (array_unique($taxonomies) as $taxonomy) {
625 + foreach ($this->get_terms_options($taxonomy) as $slug => $name) {
626 + if (!isset($options[$slug])) {
627 + $options[$slug] = $name;
628 + }
349 629 }
350 630 }
351 631
352 632 return $options;