PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.63
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.63
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 51.1.45 All 40 releases
king-addons / includes / widgets / Breadcrumbs / Breadcrumbs.php

Breadcrumbs.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.63, at includes/widgets/Breadcrumbs/Breadcrumbs.php

988 lines 29.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Breadcrumbs Widget (Free).
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Border;
12 use Elementor\Group_Control_Typography;
13 use Elementor\Repeater;
14 use Elementor\Widget_Base;
15
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * Renders the Breadcrumbs widget.
22 */
23 class Breadcrumbs extends Widget_Base
24 {
25 /**
26 * Widget slug.
27 *
28 * @return string
29 */
30 public function get_name(): string
31 {
32 return 'king-addons-breadcrumbs';
33 }
34
35 /**
36 * Widget title.
37 *
38 * @return string
39 */
40 public function get_title(): string
41 {
42 return esc_html__('Breadcrumbs', 'king-addons');
43 }
44
45 /**
46 * Widget icon.
47 *
48 * @return string
49 */
50 public function get_icon(): string
51 {
52 return 'king-addons-icon king-addons-breadcrumbs';
53 }
54
55 /**
56 * Style dependencies.
57 *
58 * @return array<int, string>
59 */
60 public function get_style_depends(): array
61 {
62 return [
63 KING_ADDONS_ASSETS_UNIQUE_KEY . '-breadcrumbs-style',
64 ];
65 }
66
67 /**
68 * Categories.
69 *
70 * @return array<int, string>
71 */
72 public function get_categories(): array
73 {
74 return ['king-addons'];
75 }
76
77 /**
78 * Keywords.
79 *
80 * @return array<int, string>
81 */
82 public function get_keywords(): array
83 {
84 return ['breadcrumb', 'navigation', 'trail', 'path', 'king-addons'];
85 }
86
87 public function get_custom_help_url()
88 {
89 return 'mailto:[email protected]?subject=Bug Report - King Addons&body=Please describe the issue';
90 }
91
92 /**
93 * Register controls.
94 *
95 * @return void
96 */
97 public function register_controls(): void
98 {
99 $this->register_content_controls();
100 $this->register_style_controls();
101 $this->register_pro_notice_controls();
102 }
103
104 /**
105 * Render output.
106 *
107 * @return void
108 */
109 public function render(): void
110 {
111 $settings = $this->get_settings_for_display();
112 $items = $this->build_breadcrumbs($settings);
113
114 if (empty($items)) {
115 return;
116 }
117
118 $this->render_output($settings, $items);
119 }
120
121 /**
122 * Content controls.
123 *
124 * @return void
125 */
126 protected function register_content_controls(): void
127 {
128 $this->start_controls_section(
129 'kng_content_section',
130 [
131 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Breadcrumbs', 'king-addons'),
132 'tab' => Controls_Manager::TAB_CONTENT,
133 ]
134 );
135
136 $this->add_control(
137 'kng_show_home',
138 [
139 'label' => esc_html__('Show Home', 'king-addons'),
140 'type' => Controls_Manager::SWITCHER,
141 'return_value' => 'yes',
142 'default' => 'yes',
143 ]
144 );
145
146 $this->add_control(
147 'kng_home_label',
148 [
149 'label' => esc_html__('Home Label', 'king-addons'),
150 'type' => Controls_Manager::TEXT,
151 'default' => esc_html__('Home', 'king-addons'),
152 'condition' => [
153 'kng_show_home' => 'yes',
154 ],
155 ]
156 );
157
158 $this->add_control(
159 'kng_separator',
160 [
161 'label' => esc_html__('Separator', 'king-addons'),
162 'type' => Controls_Manager::TEXT,
163 'default' => '/',
164 'placeholder' => '/',
165 ]
166 );
167
168 $this->add_responsive_control(
169 'kng_gap',
170 [
171 'label' => esc_html__('Gap', 'king-addons'),
172 'type' => Controls_Manager::SLIDER,
173 'size_units' => ['px'],
174 'range' => [
175 'px' => ['min' => 0, 'max' => 30],
176 ],
177 'default' => [
178 'size' => 8,
179 'unit' => 'px',
180 ],
181 'selectors' => [
182 '{{WRAPPER}} .king-addons-breadcrumbs' => '--kng-breadcrumbs-gap: {{SIZE}}{{UNIT}};',
183 ],
184 ]
185 );
186
187 $this->add_control(
188 'kng_show_current',
189 [
190 'label' => esc_html__('Show Current Item', 'king-addons'),
191 'type' => Controls_Manager::SWITCHER,
192 'return_value' => 'yes',
193 'default' => 'yes',
194 ]
195 );
196
197 $this->add_control(
198 'kng_hide_page_parents',
199 [
200 'label' => esc_html__('Hide Parent Pages', 'king-addons'),
201 'type' => Controls_Manager::SWITCHER,
202 'return_value' => 'yes',
203 'default' => '',
204 'condition' => [
205 'kng_use_custom_path!' => 'yes',
206 ],
207 ]
208 );
209
210 $this->add_control(
211 'kng_hide_post_type_archive',
212 [
213 'label' => esc_html__('Hide Post Type Archive', 'king-addons'),
214 'type' => Controls_Manager::SWITCHER,
215 'return_value' => 'yes',
216 'default' => '',
217 'condition' => [
218 'kng_use_custom_path!' => 'yes',
219 ],
220 ]
221 );
222
223 $this->add_control(
224 'kng_hide_taxonomy_segments',
225 [
226 'label' => esc_html__('Hide Taxonomy Segments', 'king-addons'),
227 'type' => Controls_Manager::SWITCHER,
228 'return_value' => 'yes',
229 'default' => '',
230 'condition' => [
231 'kng_use_custom_path!' => 'yes',
232 ],
233 ]
234 );
235
236 $this->add_control(
237 'kng_enable_schema',
238 [
239 'label' => esc_html__('JSON-LD Schema', 'king-addons'),
240 'type' => Controls_Manager::SWITCHER,
241 'return_value' => 'yes',
242 'default' => 'yes',
243 'separator' => 'before',
244 ]
245 );
246
247 $this->add_control(
248 'kng_use_custom_path',
249 [
250 'label' => esc_html__('Use Custom Path', 'king-addons'),
251 'type' => Controls_Manager::SWITCHER,
252 'return_value' => 'yes',
253 'default' => '',
254 'description' => esc_html__('Manually define breadcrumb items. Disable "Show Home" if you add Home manually.', 'king-addons'),
255 'separator' => 'before',
256 ]
257 );
258
259 $repeater = new Repeater();
260 $repeater->add_control(
261 'kng_custom_label',
262 [
263 'label' => esc_html__('Label', 'king-addons'),
264 'type' => Controls_Manager::TEXT,
265 'label_block' => true,
266 ]
267 );
268
269 $repeater->add_control(
270 'kng_custom_url',
271 [
272 'label' => esc_html__('Link', 'king-addons'),
273 'type' => Controls_Manager::URL,
274 'placeholder' => 'https://',
275 ]
276 );
277
278 $repeater->add_control(
279 'kng_custom_is_current',
280 [
281 'label' => esc_html__('Mark as Current', 'king-addons'),
282 'type' => Controls_Manager::SWITCHER,
283 'return_value' => 'yes',
284 'default' => '',
285 ]
286 );
287
288 $this->add_control(
289 'kng_custom_path',
290 [
291 'label' => esc_html__('Custom Items', 'king-addons'),
292 'type' => Controls_Manager::REPEATER,
293 'fields' => $repeater->get_controls(),
294 'title_field' => '{{ kng_custom_label }}',
295 'condition' => [
296 'kng_use_custom_path' => 'yes',
297 ],
298 ]
299 );
300
301 $this->add_control(
302 'kng_custom_append_current',
303 [
304 'label' => esc_html__('Append Current Page', 'king-addons'),
305 'type' => Controls_Manager::SWITCHER,
306 'return_value' => 'yes',
307 'default' => 'yes',
308 'description' => esc_html__('Adds the current page if no item is marked as current.', 'king-addons'),
309 'condition' => [
310 'kng_use_custom_path' => 'yes',
311 ],
312 ]
313 );
314
315 $this->end_controls_section();
316 }
317
318 /**
319 * Style controls.
320 *
321 * @return void
322 */
323 protected function register_style_controls(): void
324 {
325 $this->start_controls_section(
326 'kng_style_section',
327 [
328 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Styles', 'king-addons'),
329 'tab' => Controls_Manager::TAB_STYLE,
330 ]
331 );
332
333 $this->add_control(
334 'kng_alignment',
335 [
336 'label' => esc_html__('Alignment', 'king-addons'),
337 'type' => Controls_Manager::CHOOSE,
338 'options' => [
339 'flex-start' => [
340 'title' => esc_html__('Left', 'king-addons'),
341 'icon' => 'eicon-h-align-left',
342 ],
343 'center' => [
344 'title' => esc_html__('Center', 'king-addons'),
345 'icon' => 'eicon-h-align-center',
346 ],
347 'flex-end' => [
348 'title' => esc_html__('Right', 'king-addons'),
349 'icon' => 'eicon-h-align-right',
350 ],
351 ],
352 'toggle' => false,
353 'default' => 'flex-start',
354 'selectors' => [
355 '{{WRAPPER}} .king-addons-breadcrumbs__list' => 'justify-content: {{VALUE}};',
356 ],
357 ]
358 );
359
360 $this->add_group_control(
361 Group_Control_Typography::get_type(),
362 [
363 'name' => 'kng_typography',
364 'selector' => '{{WRAPPER}} .king-addons-breadcrumbs__link, {{WRAPPER}} .king-addons-breadcrumbs__text, {{WRAPPER}} .king-addons-breadcrumbs__separator',
365 ]
366 );
367
368 $this->add_control(
369 'kng_link_color',
370 [
371 'label' => esc_html__('Link Color', 'king-addons'),
372 'type' => Controls_Manager::COLOR,
373 'selectors' => [
374 '{{WRAPPER}} .king-addons-breadcrumbs__link' => 'color: {{VALUE}};',
375 ],
376 ]
377 );
378
379 $this->add_control(
380 'kng_link_color_hover',
381 [
382 'label' => esc_html__('Link Hover Color', 'king-addons'),
383 'type' => Controls_Manager::COLOR,
384 'selectors' => [
385 '{{WRAPPER}} .king-addons-breadcrumbs__link:hover' => 'color: {{VALUE}};',
386 ],
387 ]
388 );
389
390 $this->add_control(
391 'kng_current_color',
392 [
393 'label' => esc_html__('Current Item Color', 'king-addons'),
394 'type' => Controls_Manager::COLOR,
395 'selectors' => [
396 '{{WRAPPER}} .king-addons-breadcrumbs__current' => 'color: {{VALUE}};',
397 ],
398 ]
399 );
400
401 $this->add_control(
402 'kng_separator_color',
403 [
404 'label' => esc_html__('Separator Color', 'king-addons'),
405 'type' => Controls_Manager::COLOR,
406 'selectors' => [
407 '{{WRAPPER}} .king-addons-breadcrumbs__separator' => 'color: {{VALUE}};',
408 ],
409 ]
410 );
411
412 $this->add_group_control(
413 Group_Control_Border::get_type(),
414 [
415 'name' => 'kng_container_border',
416 'selector' => '{{WRAPPER}} .king-addons-breadcrumbs',
417 'separator' => 'before',
418 ]
419 );
420
421 $this->end_controls_section();
422 }
423
424 /**
425 * Build breadcrumbs array.
426 *
427 * @param array<string, mixed> $settings Widget settings.
428 *
429 * @return array<int, array<string, mixed>>
430 */
431 protected function build_breadcrumbs(array $settings): array
432 {
433 $items = [];
434 $show_home = ($settings['kng_show_home'] ?? 'yes') === 'yes';
435 $show_current = ($settings['kng_show_current'] ?? 'yes') === 'yes';
436 $use_custom_path = ($settings['kng_use_custom_path'] ?? '') === 'yes';
437 $home_label = $settings['kng_home_label'] ?? esc_html__('Home', 'king-addons');
438 $hide_page_parents = ($settings['kng_hide_page_parents'] ?? '') === 'yes';
439 $hide_post_type_archive = ($settings['kng_hide_post_type_archive'] ?? '') === 'yes';
440 $hide_taxonomy_segments = ($settings['kng_hide_taxonomy_segments'] ?? '') === 'yes';
441
442 if ($show_home) {
443 $items[] = $this->build_home_item($home_label);
444 }
445
446 if ($use_custom_path) {
447 $custom_items = $this->build_custom_path($settings, $show_current);
448 if (!empty($items) && !empty($custom_items) && $this->is_home_item($custom_items[0])) {
449 array_shift($custom_items);
450 }
451
452 return array_merge($items, $custom_items);
453 }
454
455 if (is_front_page()) {
456 return $items;
457 }
458
459 if (is_home()) {
460 $items[] = [
461 'label' => get_the_title(get_option('page_for_posts')) ?: esc_html__('Blog', 'king-addons'),
462 'url' => '',
463 'current' => true,
464 ];
465
466 return $items;
467 }
468
469 if (is_singular()) {
470 $post = get_post();
471 if (!$post) {
472 return $items;
473 }
474
475 $post_type = get_post_type_object(get_post_type($post));
476 if ($post_type && !in_array($post_type->name, ['post', 'page'], true) && !empty($post_type->has_archive) && !$hide_post_type_archive) {
477 $items[] = [
478 'label' => $post_type->labels->name,
479 'url' => get_post_type_archive_link($post_type->name),
480 'current' => false,
481 ];
482 }
483
484 if (is_page($post) && !$hide_page_parents) {
485 $ancestors = array_reverse(get_post_ancestors($post));
486 foreach ($ancestors as $ancestor_id) {
487 $items[] = [
488 'label' => get_the_title($ancestor_id),
489 'url' => get_permalink($ancestor_id),
490 'current' => false,
491 ];
492 }
493 } elseif ('post' === get_post_type($post) && !$hide_taxonomy_segments) {
494 $categories = get_the_category($post->ID);
495 if (!empty($categories)) {
496 $primary = $categories[0];
497 $cat_ancestors = array_reverse(get_ancestors($primary->term_id, 'category'));
498 foreach ($cat_ancestors as $cat_id) {
499 $items[] = [
500 'label' => get_cat_name($cat_id),
501 'url' => get_category_link($cat_id),
502 'current' => false,
503 ];
504 }
505
506 $items[] = [
507 'label' => $primary->name,
508 'url' => get_category_link($primary),
509 'current' => false,
510 ];
511 }
512 }
513
514 if ($show_current) {
515 $items[] = [
516 'label' => get_the_title($post),
517 'url' => '',
518 'current' => true,
519 ];
520 }
521
522 return $items;
523 }
524
525 if (is_category() || is_tag() || is_tax()) {
526 $term = get_queried_object();
527 if ($term && !is_wp_error($term)) {
528 if (!$hide_taxonomy_segments) {
529 $ancestors = array_reverse(get_ancestors($term->term_id, $term->taxonomy));
530 foreach ($ancestors as $ancestor_id) {
531 $items[] = [
532 'label' => get_term_field('name', $ancestor_id, $term->taxonomy),
533 'url' => get_term_link((int) $ancestor_id, $term->taxonomy),
534 'current' => false,
535 ];
536 }
537 }
538
539 $items[] = [
540 'label' => $term->name,
541 'url' => '',
542 'current' => true,
543 ];
544 }
545
546 return $items;
547 }
548
549 if (is_search()) {
550 $items[] = [
551 'label' => sprintf(esc_html__('Search: %s', 'king-addons'), get_search_query()),
552 'url' => '',
553 'current' => true,
554 ];
555
556 return $items;
557 }
558
559 if (is_404()) {
560 $items[] = [
561 'label' => esc_html__('404 Not Found', 'king-addons'),
562 'url' => '',
563 'current' => true,
564 ];
565
566 return $items;
567 }
568
569 if (is_author()) {
570 $items[] = [
571 'label' => esc_html__('Author', 'king-addons'),
572 'url' => '',
573 'current' => true,
574 ];
575 }
576
577 if (is_date()) {
578 if (is_year()) {
579 $items[] = [
580 'label' => get_the_date(_x('Y', 'yearly archives date format', 'king-addons')),
581 'url' => '',
582 'current' => true,
583 ];
584 } elseif (is_month()) {
585 $items[] = [
586 'label' => get_the_date(_x('F Y', 'monthly archives date format', 'king-addons')),
587 'url' => '',
588 'current' => true,
589 ];
590 } elseif (is_day()) {
591 $items[] = [
592 'label' => get_the_date(),
593 'url' => '',
594 'current' => true,
595 ];
596 }
597 }
598
599 return $items;
600 }
601
602 /**
603 * Build home item.
604 *
605 * @param string $label Home label.
606 *
607 * @return array<string, mixed>
608 */
609 protected function build_home_item(string $label): array
610 {
611 return [
612 'label' => $label,
613 'url' => home_url('/'),
614 'current' => false,
615 ];
616 }
617
618 /**
619 * Check whether an item is the home item.
620 *
621 * @param array<string, mixed> $item Breadcrumb item.
622 *
623 * @return bool
624 */
625 protected function is_home_item(array $item): bool
626 {
627 if (empty($item['url'])) {
628 return false;
629 }
630
631 $home_url = trailingslashit(home_url('/'));
632 $item_url = trailingslashit((string) $item['url']);
633
634 return $home_url === $item_url;
635 }
636
637 /**
638 * Build custom breadcrumb path from settings.
639 *
640 * @param array<string, mixed> $settings Widget settings.
641 * @param bool $show_current Whether to append current item.
642 *
643 * @return array<int, array<string, mixed>>
644 */
645 protected function build_custom_path(array $settings, bool $show_current): array
646 {
647 $items = [];
648 $custom_items = $settings['kng_custom_path'] ?? [];
649
650 foreach ($custom_items as $custom_item) {
651 $label = trim((string) ($custom_item['kng_custom_label'] ?? ''));
652 if ($label === '') {
653 continue;
654 }
655
656 $raw_url = $custom_item['kng_custom_url'] ?? '';
657 if (is_array($raw_url)) {
658 $raw_url = $raw_url['url'] ?? '';
659 }
660
661 $items[] = [
662 'label' => $label,
663 'url' => $raw_url,
664 'current' => ($custom_item['kng_custom_is_current'] ?? '') === 'yes',
665 ];
666 }
667
668 $has_current = array_filter(
669 $items,
670 static function (array $item): bool {
671 return !empty($item['current']);
672 }
673 );
674
675 if (empty($has_current) && $show_current && ($settings['kng_custom_append_current'] ?? 'yes') === 'yes') {
676 $items[] = $this->build_current_item();
677 }
678
679 return $items;
680 }
681
682 /**
683 * Build current breadcrumb item for fallback cases.
684 *
685 * @return array<string, mixed>
686 */
687 protected function build_current_item(): array
688 {
689 return [
690 'label' => $this->get_current_label(),
691 'url' => $this->get_current_url(),
692 'current' => true,
693 ];
694 }
695
696 /**
697 * Resolve label for current page.
698 *
699 * @return string
700 */
701 protected function get_current_label(): string
702 {
703 if (is_search()) {
704 return sprintf(esc_html__('Search: %s', 'king-addons'), get_search_query());
705 }
706
707 if (is_404()) {
708 return esc_html__('404 Not Found', 'king-addons');
709 }
710
711 if (is_home()) {
712 $posts_page = get_option('page_for_posts');
713 if ($posts_page) {
714 return get_the_title($posts_page) ?: esc_html__('Blog', 'king-addons');
715 }
716 }
717
718 if (is_singular()) {
719 $post = get_post();
720 if ($post) {
721 return get_the_title($post);
722 }
723 }
724
725 if (is_category() || is_tag() || is_tax()) {
726 $term = get_queried_object();
727 if ($term && !is_wp_error($term) && isset($term->name)) {
728 return (string) $term->name;
729 }
730 }
731
732 if (is_author()) {
733 $author = get_queried_object();
734 if ($author && !is_wp_error($author) && isset($author->display_name)) {
735 return (string) $author->display_name;
736 }
737
738 return esc_html__('Author', 'king-addons');
739 }
740
741 if (is_year()) {
742 return get_the_date(_x('Y', 'yearly archives date format', 'king-addons'));
743 }
744
745 if (is_month()) {
746 return get_the_date(_x('F Y', 'monthly archives date format', 'king-addons'));
747 }
748
749 if (is_day()) {
750 return get_the_date();
751 }
752
753 $title = wp_get_document_title();
754
755 return is_string($title) ? wp_strip_all_tags($title) : '';
756 }
757
758 /**
759 * Resolve current page URL.
760 *
761 * @return string
762 */
763 protected function get_current_url(): string
764 {
765 if (is_home()) {
766 $posts_page = get_option('page_for_posts');
767 if ($posts_page) {
768 return get_permalink($posts_page);
769 }
770
771 return home_url('/');
772 }
773
774 if (is_singular()) {
775 $post = get_post();
776 if ($post) {
777 return get_permalink($post);
778 }
779 }
780
781 if (is_category() || is_tag() || is_tax()) {
782 $term = get_queried_object();
783 if ($term && !is_wp_error($term)) {
784 $link = get_term_link((int) $term->term_id, $term->taxonomy);
785 if (!is_wp_error($link)) {
786 return $link;
787 }
788 }
789 }
790
791 if (is_author()) {
792 $author = get_queried_object();
793 if ($author && !is_wp_error($author) && isset($author->ID)) {
794 return get_author_posts_url((int) $author->ID);
795 }
796 }
797
798 if (is_day()) {
799 return get_day_link(get_query_var('year'), get_query_var('monthnum'), get_query_var('day'));
800 }
801
802 if (is_month()) {
803 return get_month_link(get_query_var('year'), get_query_var('monthnum'));
804 }
805
806 if (is_year()) {
807 return get_year_link(get_query_var('year'));
808 }
809
810 if (is_search()) {
811 return get_search_link();
812 }
813
814 if (is_post_type_archive()) {
815 $post_type = get_query_var('post_type');
816 if (!empty($post_type)) {
817 $archive_link = get_post_type_archive_link($post_type);
818 if ($archive_link) {
819 return $archive_link;
820 }
821 }
822 }
823
824 global $wp;
825 $request = isset($wp->request) ? $wp->request : '';
826
827 return home_url($request ? '/' . ltrim($request, '/') : '/');
828 }
829
830 /**
831 * Maybe render schema markup.
832 *
833 * @param array<string, mixed> $settings Widget settings.
834 * @param array<int, array> $items Items.
835 *
836 * @return void
837 */
838 protected function maybe_render_schema_markup(array $settings, array $items): void
839 {
840 if (!$this->is_schema_enabled($settings)) {
841 return;
842 }
843
844 $this->render_schema_markup($items);
845 }
846
847 /**
848 * Determine whether schema is enabled.
849 *
850 * @param array<string, mixed> $settings Widget settings.
851 *
852 * @return bool
853 */
854 protected function is_schema_enabled(array $settings): bool
855 {
856 if (!array_key_exists('kng_enable_schema', $settings) && !array_key_exists('kng_schema_markup', $settings)) {
857 return true;
858 }
859
860 if (($settings['kng_enable_schema'] ?? '') === 'yes') {
861 return true;
862 }
863
864 return ($settings['kng_schema_markup'] ?? '') === 'yes';
865 }
866
867 /**
868 * Render JSON-LD schema for breadcrumbs.
869 *
870 * @param array<int, array<string, mixed>> $items Breadcrumb items.
871 *
872 * @return void
873 */
874 protected function render_schema_markup(array $items): void
875 {
876 $position = 1;
877 $elements = [];
878 foreach ($items as $item) {
879 $url = $item['url'] ?? '';
880 if (empty($url) && !empty($item['current'])) {
881 $url = $this->get_current_url();
882 }
883
884 if (empty($url)) {
885 continue;
886 }
887
888 $elements[] = [
889 '@type' => 'ListItem',
890 'position' => $position,
891 'name' => wp_strip_all_tags((string) ($item['label'] ?? '')),
892 'item' => esc_url_raw($url),
893 ];
894 $position++;
895 }
896
897 if (empty($elements)) {
898 return;
899 }
900
901 $data = [
902 '@context' => 'https://schema.org',
903 '@type' => 'BreadcrumbList',
904 'itemListElement' => $elements,
905 ];
906
907 echo '<script type="application/ld+json">' . wp_json_encode($data) . '</script>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
908 }
909
910 /**
911 * Render breadcrumb markup.
912 *
913 * @param array<string, mixed> $settings Settings.
914 * @param array<int, array> $items Items.
915 *
916 * @return void
917 */
918 protected function render_output(array $settings, array $items): void
919 {
920 $separator = $settings['kng_separator'] ?? '/';
921 $schema_enabled = $this->is_schema_enabled($settings);
922 $nav_schema_attr = $schema_enabled ? ' itemscope itemtype="https://schema.org/BreadcrumbList"' : '';
923 ?>
924 <nav class="king-addons-breadcrumbs" aria-label="<?php echo esc_attr__('Breadcrumbs', 'king-addons'); ?>"<?php echo $nav_schema_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
925 <ol class="king-addons-breadcrumbs__list">
926 <?php foreach ($items as $index => $item) : ?>
927 <?php
928 $is_current = !empty($item['current']);
929 $raw_url = $item['url'] ?? '';
930 $resolved_url = $is_current && empty($raw_url) ? $this->get_current_url() : $raw_url;
931 $label = $item['label'] ?? '';
932 $position = $index + 1;
933 $item_schema_attr = $schema_enabled ? ' itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"' : '';
934 ?>
935 <li class="king-addons-breadcrumbs__item"<?php echo $item_schema_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
936 <?php if (!empty($resolved_url) && !$is_current) : ?>
937 <a class="king-addons-breadcrumbs__link" href="<?php echo esc_url($resolved_url); ?>"<?php echo $schema_enabled ? ' itemprop="item"' : ''; ?>>
938 <span class="king-addons-breadcrumbs__text"<?php echo $schema_enabled ? ' itemprop="name"' : ''; ?>>
939 <?php echo esc_html($label); ?>
940 </span>
941 </a>
942 <?php else : ?>
943 <span class="king-addons-breadcrumbs__text <?php echo $is_current ? 'king-addons-breadcrumbs__current' : ''; ?>"<?php echo $schema_enabled ? ' itemprop="name"' : ''; ?><?php echo $is_current ? ' aria-current="page"' : ''; ?>>
944 <?php echo esc_html($label); ?>
945 </span>
946 <?php endif; ?>
947 <?php if ($schema_enabled) : ?>
948 <meta itemprop="position" content="<?php echo esc_attr((string) $position); ?>"/>
949 <?php if (!empty($resolved_url)) : ?>
950 <meta itemprop="item" content="<?php echo esc_url($resolved_url); ?>"/>
951 <?php endif; ?>
952 <?php endif; ?>
953 <?php if ($index !== array_key_last($items)) : ?>
954 <span class="king-addons-breadcrumbs__separator"><?php echo esc_html($separator); ?></span>
955 <?php endif; ?>
956 </li>
957 <?php endforeach; ?>
958 </ol>
959 </nav>
960 <?php
961 $this->maybe_render_schema_markup($settings, $items);
962 }
963
964 /**
965 * Pro notice.
966 *
967 * @return void
968 */
969 public function register_pro_notice_controls(): void
970 {
971 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
972 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'breadcrumbs', [
973 'Icon separators and home icon',
974 'Prefix label and max depth',
975 'Vertical & multi-row layouts',
976 'Separator icon sizing controls',
977 ]);
978 }
979 }
980 }
981
982
983
984
985
986
987
988