PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
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 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / Testimonials_Wall / Testimonials_Wall.php

Testimonials_Wall.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.49, at includes/widgets/Testimonials_Wall/Testimonials_Wall.php

2,506 lines 90.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Testimonials Wall Widget (Free).
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Background;
12 use Elementor\Group_Control_Border;
13 use Elementor\Group_Control_Box_Shadow;
14 use Elementor\Group_Control_Image_Size;
15 use Elementor\Group_Control_Typography;
16 use Elementor\Repeater;
17 use Elementor\Utils;
18 use Elementor\Widget_Base;
19 use Elementor\Plugin;
20
21 if (!defined('ABSPATH')) {
22 exit;
23 }
24
25 /**
26 * Displays testimonials in a masonry/grid wall with filters and load more.
27 */
28 class Testimonials_Wall extends Widget_Base
29 {
30 /**
31 * Widget slug.
32 *
33 * @return string
34 */
35 public function get_name(): string
36 {
37 return 'king-addons-testimonials-wall';
38 }
39
40 /**
41 * Widget title.
42 *
43 * @return string
44 */
45 public function get_title(): string
46 {
47 return esc_html__('Testimonials Wall', 'king-addons');
48 }
49
50 /**
51 * Widget icon.
52 *
53 * @return string
54 */
55 public function get_icon(): string
56 {
57 return 'king-addons-icon king-addons-testimonials-wall';
58 }
59
60 /**
61 * Style dependencies.
62 *
63 * @return array<int, string>
64 */
65 public function get_style_depends(): array
66 {
67 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-testimonials-wall-style'];
68 }
69
70 /**
71 * Script dependencies.
72 *
73 * @return array<int, string>
74 */
75 public function get_script_depends(): array
76 {
77 $handle = KING_ADDONS_ASSETS_UNIQUE_KEY . '-testimonials-wall-script';
78
79 // Elementor may call enqueue_scripts before the widget settings are available.
80 // Therefore, get_script_depends() must not access settings.
81 // The script itself may "do nothing" if the feature is disabled via settings.
82 return [$handle];
83 }
84
85 /**
86 * Widget categories.
87 *
88 * @return array<int, string>
89 */
90 public function get_categories(): array
91 {
92 return ['king-addons'];
93 }
94
95 /**
96 * Widget keywords.
97 *
98 * @return array<int, string>
99 */
100 public function get_keywords(): array
101 {
102 return [
103 'testimonials',
104 'reviews',
105 'wall',
106 'masonry',
107 'grid',
108 'ratings',
109 'filters',
110 'load more',
111 'king',
112 'addons',
113 'kingaddons',
114 'king-addons',
115 ];
116 }
117
118 /**
119 * Widget help URL.
120 *
121 * @return string
122 */
123 public function get_custom_help_url()
124 {
125 return 'mailto:bug@kingaddons.com?subject=Bug Report - King Addons&body=Please describe the issue';
126 }
127
128 /**
129 * Register widget controls.
130 *
131 * @return void
132 */
133 public function register_controls(): void
134 {
135 $this->register_content_controls();
136 $this->register_display_controls();
137 $this->register_layout_controls();
138 $this->register_filter_controls();
139 $this->register_load_more_controls();
140 $this->register_empty_state_controls();
141 $this->register_editor_controls();
142 $this->register_style_controls();
143 $this->register_pro_notice_controls();
144 }
145
146 /**
147 * Register content controls.
148 *
149 * @return void
150 */
151 protected function register_content_controls(): void
152 {
153 $this->start_controls_section(
154 'kng_tw_content_section',
155 [
156 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Testimonials', 'king-addons'),
157 'tab' => Controls_Manager::TAB_CONTENT,
158 ]
159 );
160
161 $repeater = new Repeater();
162
163 $repeater->add_control(
164 'kng_tw_name',
165 [
166 'label' => esc_html__('Author Name', 'king-addons'),
167 'type' => Controls_Manager::TEXT,
168 'default' => esc_html__('Alex Johnson', 'king-addons'),
169 'label_block' => true,
170 ]
171 );
172
173 $repeater->add_control(
174 'kng_tw_title',
175 [
176 'label' => esc_html__('Author Title', 'king-addons'),
177 'type' => Controls_Manager::TEXT,
178 'default' => esc_html__('Product Lead', 'king-addons'),
179 'label_block' => true,
180 ]
181 );
182
183 $repeater->add_control(
184 'kng_tw_company',
185 [
186 'label' => esc_html__('Company', 'king-addons'),
187 'type' => Controls_Manager::TEXT,
188 'default' => esc_html__('Flux Labs', 'king-addons'),
189 'label_block' => true,
190 ]
191 );
192
193 $repeater->add_control(
194 'kng_tw_avatar',
195 [
196 'label' => esc_html__('Avatar', 'king-addons'),
197 'type' => Controls_Manager::MEDIA,
198 'default' => [
199 'url' => Utils::get_placeholder_image_src(),
200 ],
201 ]
202 );
203
204 $repeater->add_control(
205 'kng_tw_rating',
206 [
207 'label' => esc_html__('Rating (1-5)', 'king-addons'),
208 'type' => Controls_Manager::NUMBER,
209 'min' => 0,
210 'max' => 5,
211 'step' => 1,
212 'default' => 5,
213 ]
214 );
215
216 $repeater->add_control(
217 'kng_tw_date',
218 [
219 'label' => esc_html__('Date', 'king-addons'),
220 'type' => Controls_Manager::DATE_TIME,
221 'label_block' => true,
222 ]
223 );
224
225 $repeater->add_control(
226 'kng_tw_text',
227 [
228 'label' => esc_html__('Review Text', 'king-addons'),
229 'type' => Controls_Manager::TEXTAREA,
230 'rows' => 6,
231 'default' => esc_html__('Fast onboarding, great support, and the wall layout makes our social proof feel alive.', 'king-addons'),
232 ]
233 );
234
235 $repeater->add_control(
236 'kng_tw_source',
237 [
238 'label' => esc_html__('Source Label', 'king-addons'),
239 'type' => Controls_Manager::TEXT,
240 'default' => esc_html__('Google', 'king-addons'),
241 ]
242 );
243
244 $repeater->add_control(
245 'kng_tw_categories',
246 [
247 'label' => esc_html__('Categories (comma-separated)', 'king-addons'),
248 'type' => Controls_Manager::TEXT,
249 'default' => esc_html__('SaaS, Support', 'king-addons'),
250 'label_block' => true,
251 ]
252 );
253
254 $repeater->add_control(
255 'kng_tw_featured',
256 [
257 'label' => sprintf(__('Featured %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
258 'type' => Controls_Manager::SWITCHER,
259 'return_value' => 'yes',
260 'default' => '',
261 'separator' => 'before',
262 'classes' => 'king-addons-pro-control no-distance',
263 ]
264 );
265
266 $default_items = [
267 [
268 'kng_tw_name' => esc_html__('Alex Johnson', 'king-addons'),
269 'kng_tw_title' => esc_html__('Product Lead', 'king-addons'),
270 'kng_tw_company' => esc_html__('Flux Labs', 'king-addons'),
271 'kng_tw_rating' => 5,
272 'kng_tw_text' => esc_html__('Fast onboarding, great support, and the wall layout makes our social proof feel alive.', 'king-addons'),
273 'kng_tw_source' => esc_html__('Google', 'king-addons'),
274 'kng_tw_categories' => esc_html__('SaaS, Support', 'king-addons'),
275 ],
276 [
277 'kng_tw_name' => esc_html__('Maya Lee', 'king-addons'),
278 'kng_tw_title' => esc_html__('Founder', 'king-addons'),
279 'kng_tw_company' => esc_html__('Studio North', 'king-addons'),
280 'kng_tw_rating' => 5,
281 'kng_tw_text' => esc_html__('We replaced the old carousel with the wall and conversion improved instantly.', 'king-addons'),
282 'kng_tw_source' => esc_html__('Upwork', 'king-addons'),
283 'kng_tw_categories' => esc_html__('Agency, Conversion', 'king-addons'),
284 ],
285 [
286 'kng_tw_name' => esc_html__('Jonas Weber', 'king-addons'),
287 'kng_tw_title' => esc_html__('CX Manager', 'king-addons'),
288 'kng_tw_company' => esc_html__('Cloudline', 'king-addons'),
289 'kng_tw_rating' => 4,
290 'kng_tw_text' => esc_html__('Smooth filters and the masonry vibe make it feel modern without the heavy JS.', 'king-addons'),
291 'kng_tw_source' => esc_html__('Trustpilot', 'king-addons'),
292 'kng_tw_categories' => esc_html__('SaaS, UI', 'king-addons'),
293 ],
294 [
295 'kng_tw_name' => esc_html__('Sophia Grant', 'king-addons'),
296 'kng_tw_title' => esc_html__('Marketing Lead', 'king-addons'),
297 'kng_tw_company' => esc_html__('Orbit AI', 'king-addons'),
298 'kng_tw_rating' => 5,
299 'kng_tw_text' => esc_html__('Search + rating filters help visitors quickly find relevant stories.', 'king-addons'),
300 'kng_tw_source' => esc_html__('G2', 'king-addons'),
301 'kng_tw_categories' => esc_html__('B2B, Growth', 'king-addons'),
302 ],
303 [
304 'kng_tw_name' => esc_html__('Liam Perez', 'king-addons'),
305 'kng_tw_title' => esc_html__('Operations', 'king-addons'),
306 'kng_tw_company' => esc_html__('BrightPath', 'king-addons'),
307 'kng_tw_rating' => 4,
308 'kng_tw_text' => esc_html__('The load more feel keeps the page light but still rich in proof.', 'king-addons'),
309 'kng_tw_source' => esc_html__('LinkedIn', 'king-addons'),
310 'kng_tw_categories' => esc_html__('Services, Operations', 'king-addons'),
311 ],
312 [
313 'kng_tw_name' => esc_html__('Ivy Chen', 'king-addons'),
314 'kng_tw_title' => esc_html__('Designer', 'king-addons'),
315 'kng_tw_company' => esc_html__('Layer Studio', 'king-addons'),
316 'kng_tw_rating' => 5,
317 'kng_tw_text' => esc_html__('Love the dense layout and the subtle hover lift.', 'king-addons'),
318 'kng_tw_source' => esc_html__('Behance', 'king-addons'),
319 'kng_tw_categories' => esc_html__('Design, Studio', 'king-addons'),
320 ],
321 ];
322
323 foreach ($default_items as &$item) {
324 $item['kng_tw_avatar'] = [
325 'url' => Utils::get_placeholder_image_src(),
326 ];
327 }
328 unset($item);
329
330 $this->add_control(
331 'kng_tw_items',
332 [
333 'type' => Controls_Manager::REPEATER,
334 'fields' => $repeater->get_controls(),
335 'default' => $default_items,
336 'title_field' => '{{kng_tw_name}}',
337 ]
338 );
339
340 $this->add_group_control(
341 Group_Control_Image_Size::get_type(),
342 [
343 'name' => 'kng_tw_avatar_size',
344 'default' => 'thumbnail',
345 ]
346 );
347
348 $this->end_controls_section();
349 }
350
351 /**
352 * Register display toggles.
353 *
354 * @return void
355 */
356 protected function register_display_controls(): void
357 {
358 $this->start_controls_section(
359 'kng_tw_display_section',
360 [
361 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Display', 'king-addons'),
362 'tab' => Controls_Manager::TAB_CONTENT,
363 ]
364 );
365
366 $this->add_control(
367 'kng_tw_show_avatar',
368 [
369 'label' => esc_html__('Show Avatar', 'king-addons'),
370 'type' => Controls_Manager::SWITCHER,
371 'return_value' => 'yes',
372 'default' => 'yes',
373 ]
374 );
375
376 $this->add_control(
377 'kng_tw_show_meta',
378 [
379 'label' => esc_html__('Show Author Meta', 'king-addons'),
380 'type' => Controls_Manager::SWITCHER,
381 'return_value' => 'yes',
382 'default' => 'yes',
383 ]
384 );
385
386 $this->add_control(
387 'kng_tw_show_rating',
388 [
389 'label' => esc_html__('Show Rating', 'king-addons'),
390 'type' => Controls_Manager::SWITCHER,
391 'return_value' => 'yes',
392 'default' => 'yes',
393 ]
394 );
395
396 $this->add_control(
397 'kng_tw_show_date',
398 [
399 'label' => esc_html__('Show Date', 'king-addons'),
400 'type' => Controls_Manager::SWITCHER,
401 'return_value' => 'yes',
402 'default' => '',
403 ]
404 );
405
406 $this->add_control(
407 'kng_tw_show_source',
408 [
409 'label' => esc_html__('Show Source Label', 'king-addons'),
410 'type' => Controls_Manager::SWITCHER,
411 'return_value' => 'yes',
412 'default' => 'yes',
413 ]
414 );
415
416 $this->add_control(
417 'kng_tw_enable_read_more',
418 [
419 'label' => esc_html__('Enable Read More', 'king-addons'),
420 'type' => Controls_Manager::SWITCHER,
421 'return_value' => 'yes',
422 'default' => '',
423 ]
424 );
425
426 $this->add_control(
427 'kng_tw_read_more_lines',
428 [
429 'label' => esc_html__('Collapsed Lines', 'king-addons'),
430 'type' => Controls_Manager::NUMBER,
431 'min' => 2,
432 'max' => 12,
433 'step' => 1,
434 'default' => 4,
435 'condition' => [
436 'kng_tw_enable_read_more' => 'yes',
437 ],
438 ]
439 );
440
441 $this->add_control(
442 'kng_tw_read_more_text',
443 [
444 'label' => esc_html__('Read More Text', 'king-addons'),
445 'type' => Controls_Manager::TEXT,
446 'default' => esc_html__('Read more', 'king-addons'),
447 'condition' => [
448 'kng_tw_enable_read_more' => 'yes',
449 ],
450 ]
451 );
452
453 $this->add_control(
454 'kng_tw_read_less_text',
455 [
456 'label' => esc_html__('Read Less Text', 'king-addons'),
457 'type' => Controls_Manager::TEXT,
458 'default' => esc_html__('Read less', 'king-addons'),
459 'condition' => [
460 'kng_tw_enable_read_more' => 'yes',
461 ],
462 ]
463 );
464
465 $this->end_controls_section();
466 }
467
468 /**
469 * Register layout controls.
470 *
471 * @return void
472 */
473 protected function register_layout_controls(): void
474 {
475 $this->start_controls_section(
476 'kng_tw_layout_section',
477 [
478 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
479 'tab' => Controls_Manager::TAB_CONTENT,
480 ]
481 );
482
483 $this->add_control(
484 'kng_tw_layout',
485 [
486 'label' => esc_html__('Layout Mode', 'king-addons'),
487 'type' => Controls_Manager::SELECT,
488 'options' => [
489 'masonry' => esc_html__('Masonry', 'king-addons'),
490 'grid' => esc_html__('Grid', 'king-addons'),
491 ],
492 'default' => 'masonry',
493 ]
494 );
495
496 $this->add_responsive_control(
497 'kng_tw_columns',
498 [
499 'label' => esc_html__('Columns', 'king-addons'),
500 'type' => Controls_Manager::NUMBER,
501 'min' => 1,
502 'max' => 6,
503 'step' => 1,
504 'desktop_default' => 3,
505 'tablet_default' => 2,
506 'mobile_default' => 1,
507 'selectors' => [
508 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-cols: {{SIZE}};',
509 ],
510 ]
511 );
512
513 $this->add_responsive_control(
514 'kng_tw_gap',
515 [
516 'label' => esc_html__('Gap', 'king-addons'),
517 'type' => Controls_Manager::SLIDER,
518 'size_units' => ['px', 'em', 'rem'],
519 'range' => [
520 'px' => ['min' => 0, 'max' => 60],
521 'em' => ['min' => 0, 'max' => 3],
522 'rem' => ['min' => 0, 'max' => 3],
523 ],
524 'default' => [
525 'size' => 24,
526 'unit' => 'px',
527 ],
528 'selectors' => [
529 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-gap: {{SIZE}}{{UNIT}};',
530 ],
531 ]
532 );
533
534 $this->add_responsive_control(
535 'kng_tw_item_min_width',
536 [
537 'label' => esc_html__('Item Min Width', 'king-addons'),
538 'type' => Controls_Manager::SLIDER,
539 'size_units' => ['px', 'em', 'rem'],
540 'range' => [
541 'px' => ['min' => 120, 'max' => 480],
542 'em' => ['min' => 8, 'max' => 30],
543 'rem' => ['min' => 8, 'max' => 30],
544 ],
545 'selectors' => [
546 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-item-min: {{SIZE}}{{UNIT}};',
547 ],
548 ]
549 );
550
551 $this->add_responsive_control(
552 'kng_tw_masonry_row_height',
553 [
554 'label' => esc_html__('Masonry Row Height', 'king-addons'),
555 'type' => Controls_Manager::SLIDER,
556 'size_units' => ['px'],
557 'range' => [
558 'px' => ['min' => 4, 'max' => 20],
559 ],
560 'selectors' => [
561 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-row: {{SIZE}}{{UNIT}};',
562 ],
563 'condition' => [
564 'kng_tw_layout' => 'masonry',
565 ],
566 ]
567 );
568
569 $this->end_controls_section();
570 }
571
572 /**
573 * Register filters controls.
574 *
575 * @return void
576 */
577 protected function register_filter_controls(): void
578 {
579 $this->start_controls_section(
580 'kng_tw_filters_section',
581 [
582 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Filters', 'king-addons'),
583 'tab' => Controls_Manager::TAB_CONTENT,
584 ]
585 );
586
587 $this->add_control(
588 'kng_tw_enable_filters',
589 [
590 'label' => esc_html__('Enable Filters', 'king-addons'),
591 'type' => Controls_Manager::SWITCHER,
592 'return_value' => 'yes',
593 'default' => 'yes',
594 ]
595 );
596
597 $this->add_control(
598 'kng_tw_filters_position',
599 [
600 'label' => esc_html__('Filter Bar Position', 'king-addons'),
601 'type' => Controls_Manager::SELECT,
602 'options' => [
603 'top' => esc_html__('Top', 'king-addons'),
604 'left' => esc_html__('Left', 'king-addons'),
605 ],
606 'default' => 'top',
607 'condition' => [
608 'kng_tw_enable_filters' => 'yes',
609 ],
610 ]
611 );
612
613 $this->add_control(
614 'kng_tw_enable_category_filter',
615 [
616 'label' => esc_html__('Category Filter', 'king-addons'),
617 'type' => Controls_Manager::SWITCHER,
618 'return_value' => 'yes',
619 'default' => 'yes',
620 'condition' => [
621 'kng_tw_enable_filters' => 'yes',
622 ],
623 ]
624 );
625
626 $this->add_control(
627 'kng_tw_all_label',
628 [
629 'label' => esc_html__('All Label', 'king-addons'),
630 'type' => Controls_Manager::TEXT,
631 'default' => esc_html__('All', 'king-addons'),
632 'condition' => [
633 'kng_tw_enable_filters' => 'yes',
634 ],
635 ]
636 );
637
638 $this->add_control(
639 'kng_tw_enable_rating_filter',
640 [
641 'label' => esc_html__('Rating Filter', 'king-addons'),
642 'type' => Controls_Manager::SWITCHER,
643 'return_value' => 'yes',
644 'default' => 'yes',
645 'condition' => [
646 'kng_tw_enable_filters' => 'yes',
647 ],
648 ]
649 );
650
651 $this->add_control(
652 'kng_tw_rating_ui',
653 [
654 'label' => esc_html__('Rating UI', 'king-addons'),
655 'type' => Controls_Manager::SELECT,
656 'options' => [
657 'chips' => esc_html__('Chips', 'king-addons'),
658 'dropdown' => esc_html__('Dropdown', 'king-addons'),
659 ],
660 'default' => 'chips',
661 'condition' => [
662 'kng_tw_enable_filters' => 'yes',
663 'kng_tw_enable_rating_filter' => 'yes',
664 ],
665 ]
666 );
667
668 $this->add_control(
669 'kng_tw_enable_search',
670 [
671 'label' => esc_html__('Search Field', 'king-addons'),
672 'type' => Controls_Manager::SWITCHER,
673 'return_value' => 'yes',
674 'default' => 'yes',
675 'condition' => [
676 'kng_tw_enable_filters' => 'yes',
677 ],
678 ]
679 );
680
681 $this->add_control(
682 'kng_tw_search_placeholder',
683 [
684 'label' => esc_html__('Search Placeholder', 'king-addons'),
685 'type' => Controls_Manager::TEXT,
686 'default' => esc_html__('Search testimonials', 'king-addons'),
687 'condition' => [
688 'kng_tw_enable_filters' => 'yes',
689 'kng_tw_enable_search' => 'yes',
690 ],
691 ]
692 );
693
694 $this->add_control(
695 'kng_tw_filters_sticky',
696 [
697 'label' => sprintf(__('Sticky Filters %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
698 'type' => Controls_Manager::SWITCHER,
699 'return_value' => 'yes',
700 'default' => '',
701 'condition' => [
702 'kng_tw_enable_filters' => 'yes',
703 ],
704 'classes' => 'king-addons-pro-control no-distance',
705 ]
706 );
707
708 $this->end_controls_section();
709 }
710
711 /**
712 * Register load more controls.
713 *
714 * @return void
715 */
716 protected function register_load_more_controls(): void
717 {
718 $this->start_controls_section(
719 'kng_tw_load_more_section',
720 [
721 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Load More', 'king-addons'),
722 'tab' => Controls_Manager::TAB_CONTENT,
723 ]
724 );
725
726 $this->add_control(
727 'kng_tw_enable_load_more',
728 [
729 'label' => esc_html__('Enable Load More', 'king-addons'),
730 'type' => Controls_Manager::SWITCHER,
731 'return_value' => 'yes',
732 'default' => 'yes',
733 ]
734 );
735
736 $this->add_control(
737 'kng_tw_initial_items',
738 [
739 'label' => esc_html__('Initial Items', 'king-addons'),
740 'type' => Controls_Manager::NUMBER,
741 'min' => 6,
742 'max' => 24,
743 'step' => 1,
744 'default' => 9,
745 'condition' => [
746 'kng_tw_enable_load_more' => 'yes',
747 ],
748 ]
749 );
750
751 $this->add_control(
752 'kng_tw_items_per_load',
753 [
754 'label' => esc_html__('Items per Load', 'king-addons'),
755 'type' => Controls_Manager::NUMBER,
756 'min' => 4,
757 'max' => 20,
758 'step' => 1,
759 'default' => 6,
760 'condition' => [
761 'kng_tw_enable_load_more' => 'yes',
762 ],
763 ]
764 );
765
766 $this->add_control(
767 'kng_tw_load_more_text',
768 [
769 'label' => esc_html__('Load More Text', 'king-addons'),
770 'type' => Controls_Manager::TEXT,
771 'default' => esc_html__('Load more', 'king-addons'),
772 'condition' => [
773 'kng_tw_enable_load_more' => 'yes',
774 ],
775 ]
776 );
777
778 $this->add_control(
779 'kng_tw_loading_text',
780 [
781 'label' => esc_html__('Loading Text', 'king-addons'),
782 'type' => Controls_Manager::TEXT,
783 'default' => esc_html__('Loading...', 'king-addons'),
784 'condition' => [
785 'kng_tw_enable_load_more' => 'yes',
786 ],
787 ]
788 );
789
790 $this->add_control(
791 'kng_tw_enable_skeleton',
792 [
793 'label' => esc_html__('Skeleton Loader', 'king-addons'),
794 'type' => Controls_Manager::SWITCHER,
795 'return_value' => 'yes',
796 'default' => 'yes',
797 'condition' => [
798 'kng_tw_enable_load_more' => 'yes',
799 ],
800 ]
801 );
802
803 $this->add_control(
804 'kng_tw_skeleton_count',
805 [
806 'label' => esc_html__('Skeleton Count', 'king-addons'),
807 'type' => Controls_Manager::NUMBER,
808 'min' => 1,
809 'max' => 12,
810 'step' => 1,
811 'default' => 3,
812 'condition' => [
813 'kng_tw_enable_load_more' => 'yes',
814 'kng_tw_enable_skeleton' => 'yes',
815 ],
816 ]
817 );
818
819 $this->add_control(
820 'kng_tw_infinite_scroll',
821 [
822 'label' => sprintf(__('Infinite Scroll %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
823 'type' => Controls_Manager::SWITCHER,
824 'return_value' => 'yes',
825 'default' => '',
826 'condition' => [
827 'kng_tw_enable_load_more' => 'yes',
828 ],
829 'classes' => 'king-addons-pro-control no-distance',
830 ]
831 );
832
833 $this->end_controls_section();
834 }
835
836 /**
837 * Register empty state controls.
838 *
839 * @return void
840 */
841 protected function register_empty_state_controls(): void
842 {
843 $this->start_controls_section(
844 'kng_tw_empty_section',
845 [
846 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Empty State', 'king-addons'),
847 'tab' => Controls_Manager::TAB_CONTENT,
848 ]
849 );
850
851 $this->add_control(
852 'kng_tw_enable_empty_state',
853 [
854 'label' => esc_html__('Show Empty Message', 'king-addons'),
855 'type' => Controls_Manager::SWITCHER,
856 'return_value' => 'yes',
857 'default' => 'yes',
858 ]
859 );
860
861 $this->add_control(
862 'kng_tw_empty_text',
863 [
864 'label' => esc_html__('Empty Message', 'king-addons'),
865 'type' => Controls_Manager::TEXTAREA,
866 'default' => esc_html__('No testimonials match your filters.', 'king-addons'),
867 'condition' => [
868 'kng_tw_enable_empty_state' => 'yes',
869 ],
870 ]
871 );
872
873 $this->end_controls_section();
874 }
875
876 /**
877 * Register editor preview controls.
878 *
879 * @return void
880 */
881 protected function register_editor_controls(): void
882 {
883 $this->start_controls_section(
884 'kng_tw_editor_section',
885 [
886 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Editor Preview', 'king-addons'),
887 'tab' => Controls_Manager::TAB_CONTENT,
888 ]
889 );
890
891 $this->add_control(
892 'kng_tw_editor_notice',
893 [
894 'type' => Controls_Manager::RAW_HTML,
895 'raw' => '<div style="background: #f8f9ff; border: 1px solid #d6d9ff; border-radius: 6px; padding: 12px; margin-bottom: 10px;">
896 <strong style="display: block; margin-bottom: 6px;">' . esc_html__('Editor note', 'king-addons') . '</strong>
897 <span style="font-size: 12px; line-height: 1.4;">' . esc_html__('Live filtering and load more are optimized for the frontend. Use preview toggles if needed while editing.', 'king-addons') . '</span>
898 </div>',
899 ]
900 );
901
902 $this->add_control(
903 'kng_tw_editor_masonry_preview',
904 [
905 'label' => esc_html__('Enable Masonry Preview', 'king-addons'),
906 'type' => Controls_Manager::SWITCHER,
907 'return_value' => 'yes',
908 'default' => '',
909 ]
910 );
911
912 $this->add_control(
913 'kng_tw_editor_load_more_preview',
914 [
915 'label' => esc_html__('Enable Load More Preview', 'king-addons'),
916 'type' => Controls_Manager::SWITCHER,
917 'return_value' => 'yes',
918 'default' => '',
919 ]
920 );
921
922 $this->add_control(
923 'kng_tw_editor_max_items',
924 [
925 'label' => esc_html__('Max Items in Editor', 'king-addons'),
926 'type' => Controls_Manager::NUMBER,
927 'min' => 4,
928 'max' => 20,
929 'step' => 1,
930 'default' => 8,
931 ]
932 );
933
934 $this->end_controls_section();
935 }
936
937 /**
938 * Register style controls.
939 *
940 * @return void
941 */
942 protected function register_style_controls(): void
943 {
944 $this->register_style_wall_controls();
945 $this->register_style_card_controls();
946 $this->register_style_typography_controls();
947 $this->register_style_avatar_controls();
948 $this->register_style_rating_controls();
949 $this->register_style_filter_controls();
950 $this->register_style_read_more_controls();
951 $this->register_style_load_more_controls();
952 $this->register_style_empty_state_controls();
953 $this->register_style_skeleton_controls();
954 }
955
956 /**
957 * Wall style controls.
958 *
959 * @return void
960 */
961 protected function register_style_wall_controls(): void
962 {
963 $this->start_controls_section(
964 'kng_tw_style_wall',
965 [
966 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Wall', 'king-addons'),
967 'tab' => Controls_Manager::TAB_STYLE,
968 ]
969 );
970
971 $this->add_group_control(
972 Group_Control_Background::get_type(),
973 [
974 'name' => 'kng_tw_wall_bg',
975 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall',
976 ]
977 );
978
979 $this->add_responsive_control(
980 'kng_tw_wall_padding',
981 [
982 'label' => esc_html__('Padding', 'king-addons'),
983 'type' => Controls_Manager::DIMENSIONS,
984 'size_units' => ['px', 'em', 'rem'],
985 'selectors' => [
986 '{{WRAPPER}} .king-addons-testimonials-wall' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
987 ],
988 ]
989 );
990
991 $this->add_responsive_control(
992 'kng_tw_wall_max_width',
993 [
994 'label' => esc_html__('Content Width', 'king-addons'),
995 'type' => Controls_Manager::SLIDER,
996 'size_units' => ['px', '%'],
997 'range' => [
998 'px' => ['min' => 320, 'max' => 1600],
999 '%' => ['min' => 50, 'max' => 100],
1000 ],
1001 'selectors' => [
1002 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-max-width: {{SIZE}}{{UNIT}};',
1003 ],
1004 ]
1005 );
1006
1007 $this->add_control(
1008 'kng_tw_text_color',
1009 [
1010 'label' => esc_html__('Text Color', 'king-addons'),
1011 'type' => Controls_Manager::COLOR,
1012 'selectors' => [
1013 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-text-color: {{VALUE}};',
1014 ],
1015 ]
1016 );
1017
1018 $this->add_control(
1019 'kng_tw_muted_color',
1020 [
1021 'label' => esc_html__('Muted Color', 'king-addons'),
1022 'type' => Controls_Manager::COLOR,
1023 'selectors' => [
1024 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-muted-color: {{VALUE}};',
1025 ],
1026 ]
1027 );
1028
1029 $this->add_control(
1030 'kng_tw_border_color',
1031 [
1032 'label' => esc_html__('Border Color', 'king-addons'),
1033 'type' => Controls_Manager::COLOR,
1034 'selectors' => [
1035 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-border-color: {{VALUE}};',
1036 ],
1037 ]
1038 );
1039
1040 $this->end_controls_section();
1041 }
1042
1043 /**
1044 * Card style controls.
1045 *
1046 * @return void
1047 */
1048 protected function register_style_card_controls(): void
1049 {
1050 $this->start_controls_section(
1051 'kng_tw_style_card',
1052 [
1053 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Card', 'king-addons'),
1054 'tab' => Controls_Manager::TAB_STYLE,
1055 ]
1056 );
1057
1058 $this->add_control(
1059 'kng_tw_card_bg',
1060 [
1061 'label' => esc_html__('Background', 'king-addons'),
1062 'type' => Controls_Manager::COLOR,
1063 'selectors' => [
1064 '{{WRAPPER}} .king-addons-testimonials-wall__card' => 'background-color: {{VALUE}};',
1065 ],
1066 ]
1067 );
1068
1069 $this->add_group_control(
1070 Group_Control_Border::get_type(),
1071 [
1072 'name' => 'kng_tw_card_border',
1073 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__card',
1074 ]
1075 );
1076
1077 $this->add_responsive_control(
1078 'kng_tw_card_radius',
1079 [
1080 'label' => esc_html__('Border Radius', 'king-addons'),
1081 'type' => Controls_Manager::SLIDER,
1082 'size_units' => ['px', '%'],
1083 'selectors' => [
1084 '{{WRAPPER}} .king-addons-testimonials-wall__card' => 'border-radius: {{SIZE}}{{UNIT}};',
1085 ],
1086 ]
1087 );
1088
1089 $this->add_group_control(
1090 Group_Control_Box_Shadow::get_type(),
1091 [
1092 'name' => 'kng_tw_card_shadow',
1093 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__card',
1094 ]
1095 );
1096
1097 $this->add_group_control(
1098 Group_Control_Box_Shadow::get_type(),
1099 [
1100 'name' => 'kng_tw_card_shadow_hover',
1101 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__card:hover',
1102 ]
1103 );
1104
1105 $this->add_responsive_control(
1106 'kng_tw_card_hover_translate',
1107 [
1108 'label' => esc_html__('Hover Lift', 'king-addons'),
1109 'type' => Controls_Manager::SLIDER,
1110 'size_units' => ['px'],
1111 'range' => [
1112 'px' => ['min' => 0, 'max' => 20],
1113 ],
1114 'selectors' => [
1115 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-card-hover-translate: {{SIZE}}{{UNIT}};',
1116 ],
1117 ]
1118 );
1119
1120 $this->add_control(
1121 'kng_tw_card_hover_duration',
1122 [
1123 'label' => esc_html__('Hover Transition (s)', 'king-addons'),
1124 'type' => Controls_Manager::NUMBER,
1125 'min' => 0,
1126 'max' => 2,
1127 'step' => 0.05,
1128 'default' => 0.2,
1129 'selectors' => [
1130 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-card-hover-duration: {{VALUE}}s;',
1131 ],
1132 ]
1133 );
1134
1135 $this->add_responsive_control(
1136 'kng_tw_card_padding',
1137 [
1138 'label' => esc_html__('Padding', 'king-addons'),
1139 'type' => Controls_Manager::DIMENSIONS,
1140 'size_units' => ['px', 'em', 'rem'],
1141 'selectors' => [
1142 '{{WRAPPER}} .king-addons-testimonials-wall__card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1143 ],
1144 ]
1145 );
1146
1147 $this->end_controls_section();
1148 }
1149
1150 /**
1151 * Typography style controls.
1152 *
1153 * @return void
1154 */
1155 protected function register_style_typography_controls(): void
1156 {
1157 $this->start_controls_section(
1158 'kng_tw_style_typography',
1159 [
1160 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Typography', 'king-addons'),
1161 'tab' => Controls_Manager::TAB_STYLE,
1162 ]
1163 );
1164
1165 $this->add_group_control(
1166 Group_Control_Typography::get_type(),
1167 [
1168 'name' => 'kng_tw_name_typography',
1169 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__name',
1170 ]
1171 );
1172
1173 $this->add_group_control(
1174 Group_Control_Typography::get_type(),
1175 [
1176 'name' => 'kng_tw_meta_typography',
1177 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__meta',
1178 ]
1179 );
1180
1181 $this->add_group_control(
1182 Group_Control_Typography::get_type(),
1183 [
1184 'name' => 'kng_tw_text_typography',
1185 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__text',
1186 ]
1187 );
1188
1189 $this->add_group_control(
1190 Group_Control_Typography::get_type(),
1191 [
1192 'name' => 'kng_tw_source_typography',
1193 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__source',
1194 ]
1195 );
1196
1197 $this->add_group_control(
1198 Group_Control_Typography::get_type(),
1199 [
1200 'name' => 'kng_tw_date_typography',
1201 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__date',
1202 ]
1203 );
1204
1205 $this->end_controls_section();
1206 }
1207
1208 /**
1209 * Avatar style controls.
1210 *
1211 * @return void
1212 */
1213 protected function register_style_avatar_controls(): void
1214 {
1215 $this->start_controls_section(
1216 'kng_tw_style_avatar',
1217 [
1218 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Avatar', 'king-addons'),
1219 'tab' => Controls_Manager::TAB_STYLE,
1220 ]
1221 );
1222
1223 $this->add_responsive_control(
1224 'kng_tw_avatar_box_size',
1225 [
1226 'label' => esc_html__('Size', 'king-addons'),
1227 'type' => Controls_Manager::SLIDER,
1228 'size_units' => ['px', 'em', 'rem'],
1229 'range' => [
1230 'px' => ['min' => 24, 'max' => 120],
1231 'em' => ['min' => 2, 'max' => 8],
1232 'rem' => ['min' => 2, 'max' => 8],
1233 ],
1234 'selectors' => [
1235 '{{WRAPPER}} .king-addons-testimonials-wall__avatar' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
1236 ],
1237 ]
1238 );
1239
1240 $this->add_responsive_control(
1241 'kng_tw_avatar_radius',
1242 [
1243 'label' => esc_html__('Border Radius', 'king-addons'),
1244 'type' => Controls_Manager::SLIDER,
1245 'size_units' => ['px', '%'],
1246 'selectors' => [
1247 '{{WRAPPER}} .king-addons-testimonials-wall__avatar' => 'border-radius: {{SIZE}}{{UNIT}};',
1248 '{{WRAPPER}} .king-addons-testimonials-wall__avatar img' => 'border-radius: {{SIZE}}{{UNIT}};',
1249 ],
1250 ]
1251 );
1252
1253 $this->add_group_control(
1254 Group_Control_Border::get_type(),
1255 [
1256 'name' => 'kng_tw_avatar_border',
1257 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__avatar',
1258 ]
1259 );
1260
1261 $this->end_controls_section();
1262 }
1263
1264 /**
1265 * Rating style controls.
1266 *
1267 * @return void
1268 */
1269 protected function register_style_rating_controls(): void
1270 {
1271 $this->start_controls_section(
1272 'kng_tw_style_rating',
1273 [
1274 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Rating', 'king-addons'),
1275 'tab' => Controls_Manager::TAB_STYLE,
1276 ]
1277 );
1278
1279 $this->add_responsive_control(
1280 'kng_tw_star_size',
1281 [
1282 'label' => esc_html__('Star Size', 'king-addons'),
1283 'type' => Controls_Manager::SLIDER,
1284 'size_units' => ['px', 'em', 'rem'],
1285 'selectors' => [
1286 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-star-size: {{SIZE}}{{UNIT}};',
1287 ],
1288 ]
1289 );
1290
1291 $this->add_control(
1292 'kng_tw_star_color',
1293 [
1294 'label' => esc_html__('Star Color', 'king-addons'),
1295 'type' => Controls_Manager::COLOR,
1296 'selectors' => [
1297 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-star-color: {{VALUE}};',
1298 ],
1299 ]
1300 );
1301
1302 $this->add_control(
1303 'kng_tw_star_empty_color',
1304 [
1305 'label' => esc_html__('Empty Star Color', 'king-addons'),
1306 'type' => Controls_Manager::COLOR,
1307 'selectors' => [
1308 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-star-muted: {{VALUE}};',
1309 ],
1310 ]
1311 );
1312
1313 $this->add_responsive_control(
1314 'kng_tw_star_gap',
1315 [
1316 'label' => esc_html__('Star Gap', 'king-addons'),
1317 'type' => Controls_Manager::SLIDER,
1318 'size_units' => ['px', 'em', 'rem'],
1319 'selectors' => [
1320 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-star-gap: {{SIZE}}{{UNIT}};',
1321 ],
1322 ]
1323 );
1324
1325 $this->end_controls_section();
1326 }
1327
1328 /**
1329 * Filters style controls.
1330 *
1331 * @return void
1332 */
1333 protected function register_style_filter_controls(): void
1334 {
1335 $this->start_controls_section(
1336 'kng_tw_style_filters',
1337 [
1338 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Filters', 'king-addons'),
1339 'tab' => Controls_Manager::TAB_STYLE,
1340 ]
1341 );
1342
1343 $this->add_control(
1344 'kng_tw_chip_style',
1345 [
1346 'label' => esc_html__('Chip Style', 'king-addons'),
1347 'type' => Controls_Manager::SELECT,
1348 'options' => [
1349 'filled' => esc_html__('Filled', 'king-addons'),
1350 'outline' => esc_html__('Outline', 'king-addons'),
1351 ],
1352 'default' => 'filled',
1353 ]
1354 );
1355
1356 $this->add_control(
1357 'kng_tw_filters_alignment',
1358 [
1359 'label' => esc_html__('Filters Alignment', 'king-addons'),
1360 'type' => Controls_Manager::SELECT,
1361 'options' => [
1362 'flex-start' => esc_html__('Left', 'king-addons'),
1363 'center' => esc_html__('Center', 'king-addons'),
1364 'flex-end' => esc_html__('Right', 'king-addons'),
1365 'space-between' => esc_html__('Space Between', 'king-addons'),
1366 ],
1367 'default' => 'flex-start',
1368 'selectors' => [
1369 '{{WRAPPER}} .king-addons-testimonials-wall__filters' => 'justify-content: {{VALUE}};',
1370 ],
1371 ]
1372 );
1373
1374 $this->add_responsive_control(
1375 'kng_tw_filters_gap',
1376 [
1377 'label' => esc_html__('Filter Bar Gap', 'king-addons'),
1378 'type' => Controls_Manager::SLIDER,
1379 'size_units' => ['px', 'em', 'rem'],
1380 'range' => [
1381 'px' => ['min' => 0, 'max' => 40],
1382 'em' => ['min' => 0, 'max' => 3],
1383 'rem' => ['min' => 0, 'max' => 3],
1384 ],
1385 'selectors' => [
1386 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-filters-gap: {{SIZE}}{{UNIT}};',
1387 ],
1388 ]
1389 );
1390
1391 $this->add_responsive_control(
1392 'kng_tw_filter_group_gap',
1393 [
1394 'label' => esc_html__('Filter Group Gap', 'king-addons'),
1395 'type' => Controls_Manager::SLIDER,
1396 'size_units' => ['px', 'em', 'rem'],
1397 'range' => [
1398 'px' => ['min' => 0, 'max' => 30],
1399 'em' => ['min' => 0, 'max' => 2],
1400 'rem' => ['min' => 0, 'max' => 2],
1401 ],
1402 'selectors' => [
1403 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-filter-group-gap: {{SIZE}}{{UNIT}};',
1404 ],
1405 ]
1406 );
1407
1408 $this->add_group_control(
1409 Group_Control_Typography::get_type(),
1410 [
1411 'name' => 'kng_tw_chip_typography',
1412 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__filter-chip',
1413 ]
1414 );
1415
1416 $this->add_control(
1417 'kng_tw_chip_text_color',
1418 [
1419 'label' => esc_html__('Chip Text Color', 'king-addons'),
1420 'type' => Controls_Manager::COLOR,
1421 'selectors' => [
1422 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-text: {{VALUE}};',
1423 ],
1424 ]
1425 );
1426
1427 $this->add_control(
1428 'kng_tw_chip_bg_color',
1429 [
1430 'label' => esc_html__('Chip Background', 'king-addons'),
1431 'type' => Controls_Manager::COLOR,
1432 'selectors' => [
1433 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-bg: {{VALUE}};',
1434 ],
1435 ]
1436 );
1437
1438 $this->add_control(
1439 'kng_tw_chip_border_color',
1440 [
1441 'label' => esc_html__('Chip Border', 'king-addons'),
1442 'type' => Controls_Manager::COLOR,
1443 'selectors' => [
1444 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-border: {{VALUE}};',
1445 ],
1446 ]
1447 );
1448
1449 $this->add_control(
1450 'kng_tw_chip_active_text_color',
1451 [
1452 'label' => esc_html__('Active Text', 'king-addons'),
1453 'type' => Controls_Manager::COLOR,
1454 'selectors' => [
1455 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-active-text: {{VALUE}};',
1456 ],
1457 ]
1458 );
1459
1460 $this->add_control(
1461 'kng_tw_chip_active_bg_color',
1462 [
1463 'label' => esc_html__('Active Background', 'king-addons'),
1464 'type' => Controls_Manager::COLOR,
1465 'selectors' => [
1466 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-active-bg: {{VALUE}};',
1467 ],
1468 ]
1469 );
1470
1471 $this->add_control(
1472 'kng_tw_chip_active_border_color',
1473 [
1474 'label' => esc_html__('Active Border', 'king-addons'),
1475 'type' => Controls_Manager::COLOR,
1476 'selectors' => [
1477 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-chip-active-border: {{VALUE}};',
1478 ],
1479 ]
1480 );
1481
1482 $this->add_responsive_control(
1483 'kng_tw_chip_radius',
1484 [
1485 'label' => esc_html__('Chip Radius', 'king-addons'),
1486 'type' => Controls_Manager::SLIDER,
1487 'size_units' => ['px', '%'],
1488 'selectors' => [
1489 '{{WRAPPER}} .king-addons-testimonials-wall__filter-chip' => 'border-radius: {{SIZE}}{{UNIT}};',
1490 ],
1491 ]
1492 );
1493
1494 $this->add_responsive_control(
1495 'kng_tw_chip_padding',
1496 [
1497 'label' => esc_html__('Chip Padding', 'king-addons'),
1498 'type' => Controls_Manager::DIMENSIONS,
1499 'size_units' => ['px', 'em', 'rem'],
1500 'selectors' => [
1501 '{{WRAPPER}} .king-addons-testimonials-wall__filter-chip' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1502 ],
1503 ]
1504 );
1505
1506 $this->add_group_control(
1507 Group_Control_Typography::get_type(),
1508 [
1509 'name' => 'kng_tw_search_typography',
1510 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__search-input',
1511 ]
1512 );
1513
1514 $this->add_responsive_control(
1515 'kng_tw_search_padding',
1516 [
1517 'label' => esc_html__('Search Padding', 'king-addons'),
1518 'type' => Controls_Manager::DIMENSIONS,
1519 'size_units' => ['px', 'em', 'rem'],
1520 'selectors' => [
1521 '{{WRAPPER}} .king-addons-testimonials-wall__search-input' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1522 ],
1523 ]
1524 );
1525
1526 $this->add_control(
1527 'kng_tw_search_bg',
1528 [
1529 'label' => esc_html__('Search Background', 'king-addons'),
1530 'type' => Controls_Manager::COLOR,
1531 'selectors' => [
1532 '{{WRAPPER}} .king-addons-testimonials-wall__search-input' => 'background-color: {{VALUE}};',
1533 ],
1534 ]
1535 );
1536
1537 $this->add_control(
1538 'kng_tw_search_text',
1539 [
1540 'label' => esc_html__('Search Text Color', 'king-addons'),
1541 'type' => Controls_Manager::COLOR,
1542 'selectors' => [
1543 '{{WRAPPER}} .king-addons-testimonials-wall__search-input' => 'color: {{VALUE}};',
1544 ],
1545 ]
1546 );
1547
1548 $this->add_control(
1549 'kng_tw_search_border',
1550 [
1551 'label' => esc_html__('Search Border Color', 'king-addons'),
1552 'type' => Controls_Manager::COLOR,
1553 'selectors' => [
1554 '{{WRAPPER}} .king-addons-testimonials-wall__search-input' => 'border-color: {{VALUE}};',
1555 ],
1556 ]
1557 );
1558
1559 $this->add_responsive_control(
1560 'kng_tw_search_radius',
1561 [
1562 'label' => esc_html__('Search Radius', 'king-addons'),
1563 'type' => Controls_Manager::SLIDER,
1564 'size_units' => ['px', '%'],
1565 'selectors' => [
1566 '{{WRAPPER}} .king-addons-testimonials-wall__search-input' => 'border-radius: {{SIZE}}{{UNIT}};',
1567 ],
1568 ]
1569 );
1570
1571 $this->add_group_control(
1572 Group_Control_Typography::get_type(),
1573 [
1574 'name' => 'kng_tw_dropdown_typography',
1575 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__rating-select',
1576 ]
1577 );
1578
1579 $this->add_responsive_control(
1580 'kng_tw_dropdown_padding',
1581 [
1582 'label' => esc_html__('Dropdown Padding', 'king-addons'),
1583 'type' => Controls_Manager::DIMENSIONS,
1584 'size_units' => ['px', 'em', 'rem'],
1585 'selectors' => [
1586 '{{WRAPPER}} .king-addons-testimonials-wall__rating-select' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1587 ],
1588 ]
1589 );
1590
1591 $this->add_control(
1592 'kng_tw_dropdown_bg',
1593 [
1594 'label' => esc_html__('Dropdown Background', 'king-addons'),
1595 'type' => Controls_Manager::COLOR,
1596 'selectors' => [
1597 '{{WRAPPER}} .king-addons-testimonials-wall__rating-select' => 'background-color: {{VALUE}};',
1598 ],
1599 ]
1600 );
1601
1602 $this->add_control(
1603 'kng_tw_dropdown_text',
1604 [
1605 'label' => esc_html__('Dropdown Text Color', 'king-addons'),
1606 'type' => Controls_Manager::COLOR,
1607 'selectors' => [
1608 '{{WRAPPER}} .king-addons-testimonials-wall__rating-select' => 'color: {{VALUE}};',
1609 ],
1610 ]
1611 );
1612
1613 $this->add_control(
1614 'kng_tw_dropdown_border',
1615 [
1616 'label' => esc_html__('Dropdown Border', 'king-addons'),
1617 'type' => Controls_Manager::COLOR,
1618 'selectors' => [
1619 '{{WRAPPER}} .king-addons-testimonials-wall__rating-select' => 'border-color: {{VALUE}};',
1620 ],
1621 ]
1622 );
1623
1624 $this->add_responsive_control(
1625 'kng_tw_dropdown_radius',
1626 [
1627 'label' => esc_html__('Dropdown Radius', 'king-addons'),
1628 'type' => Controls_Manager::SLIDER,
1629 'size_units' => ['px', '%'],
1630 'selectors' => [
1631 '{{WRAPPER}} .king-addons-testimonials-wall__rating-select' => 'border-radius: {{SIZE}}{{UNIT}};',
1632 ],
1633 ]
1634 );
1635
1636 $this->end_controls_section();
1637 }
1638
1639 /**
1640 * Read more style controls.
1641 *
1642 * @return void
1643 */
1644 protected function register_style_read_more_controls(): void
1645 {
1646 $this->start_controls_section(
1647 'kng_tw_style_read_more',
1648 [
1649 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Read More', 'king-addons'),
1650 'tab' => Controls_Manager::TAB_STYLE,
1651 ]
1652 );
1653
1654 $this->add_group_control(
1655 Group_Control_Typography::get_type(),
1656 [
1657 'name' => 'kng_tw_read_more_typography',
1658 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__read-more',
1659 ]
1660 );
1661
1662 $this->start_controls_tabs('kng_tw_read_more_tabs');
1663
1664 $this->start_controls_tab(
1665 'kng_tw_read_more_tab_normal',
1666 [
1667 'label' => esc_html__('Normal', 'king-addons'),
1668 ]
1669 );
1670
1671 $this->add_control(
1672 'kng_tw_read_more_color',
1673 [
1674 'label' => esc_html__('Text Color', 'king-addons'),
1675 'type' => Controls_Manager::COLOR,
1676 'selectors' => [
1677 '{{WRAPPER}} .king-addons-testimonials-wall__read-more' => 'color: {{VALUE}};',
1678 ],
1679 ]
1680 );
1681
1682 $this->end_controls_tab();
1683
1684 $this->start_controls_tab(
1685 'kng_tw_read_more_tab_hover',
1686 [
1687 'label' => esc_html__('Hover', 'king-addons'),
1688 ]
1689 );
1690
1691 $this->add_control(
1692 'kng_tw_read_more_color_hover',
1693 [
1694 'label' => esc_html__('Text Color', 'king-addons'),
1695 'type' => Controls_Manager::COLOR,
1696 'selectors' => [
1697 '{{WRAPPER}} .king-addons-testimonials-wall__read-more:hover' => 'color: {{VALUE}};',
1698 ],
1699 ]
1700 );
1701
1702 $this->end_controls_tab();
1703 $this->end_controls_tabs();
1704
1705 $this->add_responsive_control(
1706 'kng_tw_read_more_spacing',
1707 [
1708 'label' => esc_html__('Spacing', 'king-addons'),
1709 'type' => Controls_Manager::SLIDER,
1710 'size_units' => ['px', 'em', 'rem'],
1711 'range' => [
1712 'px' => ['min' => 0, 'max' => 30],
1713 'em' => ['min' => 0, 'max' => 2],
1714 'rem' => ['min' => 0, 'max' => 2],
1715 ],
1716 'selectors' => [
1717 '{{WRAPPER}} .king-addons-testimonials-wall__read-more' => 'margin-top: {{SIZE}}{{UNIT}};',
1718 ],
1719 ]
1720 );
1721
1722 $this->end_controls_section();
1723 }
1724
1725 /**
1726 * Load more button style controls.
1727 *
1728 * @return void
1729 */
1730 protected function register_style_load_more_controls(): void
1731 {
1732 $this->start_controls_section(
1733 'kng_tw_style_load_more',
1734 [
1735 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Load More Button', 'king-addons'),
1736 'tab' => Controls_Manager::TAB_STYLE,
1737 ]
1738 );
1739
1740 $this->add_group_control(
1741 Group_Control_Typography::get_type(),
1742 [
1743 'name' => 'kng_tw_load_more_typography',
1744 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn',
1745 ]
1746 );
1747
1748 $this->add_group_control(
1749 Group_Control_Border::get_type(),
1750 [
1751 'name' => 'kng_tw_load_more_border',
1752 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn',
1753 ]
1754 );
1755
1756 $this->add_group_control(
1757 Group_Control_Box_Shadow::get_type(),
1758 [
1759 'name' => 'kng_tw_load_more_shadow',
1760 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn',
1761 ]
1762 );
1763
1764 $this->start_controls_tabs('kng_tw_load_more_tabs');
1765
1766 $this->start_controls_tab(
1767 'kng_tw_load_more_tab_normal',
1768 [
1769 'label' => esc_html__('Normal', 'king-addons'),
1770 ]
1771 );
1772
1773 $this->add_control(
1774 'kng_tw_load_more_text_color',
1775 [
1776 'label' => esc_html__('Text Color', 'king-addons'),
1777 'type' => Controls_Manager::COLOR,
1778 'selectors' => [
1779 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn' => 'color: {{VALUE}};',
1780 ],
1781 ]
1782 );
1783
1784 $this->add_control(
1785 'kng_tw_load_more_bg',
1786 [
1787 'label' => esc_html__('Background Color', 'king-addons'),
1788 'type' => Controls_Manager::COLOR,
1789 'selectors' => [
1790 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn' => 'background-color: {{VALUE}};',
1791 ],
1792 ]
1793 );
1794
1795 $this->end_controls_tab();
1796
1797 $this->start_controls_tab(
1798 'kng_tw_load_more_tab_hover',
1799 [
1800 'label' => esc_html__('Hover', 'king-addons'),
1801 ]
1802 );
1803
1804 $this->add_control(
1805 'kng_tw_load_more_text_color_hover',
1806 [
1807 'label' => esc_html__('Text Color', 'king-addons'),
1808 'type' => Controls_Manager::COLOR,
1809 'selectors' => [
1810 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn:hover' => 'color: {{VALUE}};',
1811 ],
1812 ]
1813 );
1814
1815 $this->add_control(
1816 'kng_tw_load_more_bg_hover',
1817 [
1818 'label' => esc_html__('Background Color', 'king-addons'),
1819 'type' => Controls_Manager::COLOR,
1820 'selectors' => [
1821 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn:hover' => 'background-color: {{VALUE}};',
1822 ],
1823 ]
1824 );
1825
1826 $this->add_control(
1827 'kng_tw_load_more_border_hover',
1828 [
1829 'label' => esc_html__('Border Color', 'king-addons'),
1830 'type' => Controls_Manager::COLOR,
1831 'selectors' => [
1832 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn:hover' => 'border-color: {{VALUE}};',
1833 ],
1834 ]
1835 );
1836
1837 $this->add_group_control(
1838 Group_Control_Box_Shadow::get_type(),
1839 [
1840 'name' => 'kng_tw_load_more_shadow_hover',
1841 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn:hover',
1842 ]
1843 );
1844
1845 $this->end_controls_tab();
1846 $this->end_controls_tabs();
1847
1848 $this->add_responsive_control(
1849 'kng_tw_load_more_padding',
1850 [
1851 'label' => esc_html__('Padding', 'king-addons'),
1852 'type' => Controls_Manager::DIMENSIONS,
1853 'size_units' => ['px', 'em', 'rem'],
1854 'selectors' => [
1855 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1856 ],
1857 ]
1858 );
1859
1860 $this->add_responsive_control(
1861 'kng_tw_load_more_radius',
1862 [
1863 'label' => esc_html__('Border Radius', 'king-addons'),
1864 'type' => Controls_Manager::SLIDER,
1865 'size_units' => ['px', '%'],
1866 'selectors' => [
1867 '{{WRAPPER}} .king-addons-testimonials-wall__load-more-btn' => 'border-radius: {{SIZE}}{{UNIT}};',
1868 ],
1869 ]
1870 );
1871
1872 $this->add_control(
1873 'kng_tw_load_more_alignment',
1874 [
1875 'label' => esc_html__('Alignment', 'king-addons'),
1876 'type' => Controls_Manager::SELECT,
1877 'options' => [
1878 'flex-start' => esc_html__('Left', 'king-addons'),
1879 'center' => esc_html__('Center', 'king-addons'),
1880 'flex-end' => esc_html__('Right', 'king-addons'),
1881 ],
1882 'default' => 'center',
1883 'selectors' => [
1884 '{{WRAPPER}} .king-addons-testimonials-wall__load-more' => 'justify-content: {{VALUE}};',
1885 ],
1886 'separator' => 'before',
1887 ]
1888 );
1889
1890 $this->end_controls_section();
1891 }
1892
1893 /**
1894 * Empty state style controls.
1895 *
1896 * @return void
1897 */
1898 protected function register_style_empty_state_controls(): void
1899 {
1900 $this->start_controls_section(
1901 'kng_tw_style_empty',
1902 [
1903 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Empty State', 'king-addons'),
1904 'tab' => Controls_Manager::TAB_STYLE,
1905 ]
1906 );
1907
1908 $this->add_group_control(
1909 Group_Control_Typography::get_type(),
1910 [
1911 'name' => 'kng_tw_empty_typography',
1912 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__empty',
1913 ]
1914 );
1915
1916 $this->add_control(
1917 'kng_tw_empty_text_color',
1918 [
1919 'label' => esc_html__('Text Color', 'king-addons'),
1920 'type' => Controls_Manager::COLOR,
1921 'selectors' => [
1922 '{{WRAPPER}} .king-addons-testimonials-wall__empty' => 'color: {{VALUE}};',
1923 ],
1924 ]
1925 );
1926
1927 $this->add_control(
1928 'kng_tw_empty_bg',
1929 [
1930 'label' => esc_html__('Background Color', 'king-addons'),
1931 'type' => Controls_Manager::COLOR,
1932 'selectors' => [
1933 '{{WRAPPER}} .king-addons-testimonials-wall__empty' => 'background-color: {{VALUE}};',
1934 ],
1935 ]
1936 );
1937
1938 $this->add_group_control(
1939 Group_Control_Border::get_type(),
1940 [
1941 'name' => 'kng_tw_empty_border',
1942 'selector' => '{{WRAPPER}} .king-addons-testimonials-wall__empty',
1943 ]
1944 );
1945
1946 $this->add_responsive_control(
1947 'kng_tw_empty_radius',
1948 [
1949 'label' => esc_html__('Border Radius', 'king-addons'),
1950 'type' => Controls_Manager::SLIDER,
1951 'size_units' => ['px', '%'],
1952 'selectors' => [
1953 '{{WRAPPER}} .king-addons-testimonials-wall__empty' => 'border-radius: {{SIZE}}{{UNIT}};',
1954 ],
1955 ]
1956 );
1957
1958 $this->add_responsive_control(
1959 'kng_tw_empty_padding',
1960 [
1961 'label' => esc_html__('Padding', 'king-addons'),
1962 'type' => Controls_Manager::DIMENSIONS,
1963 'size_units' => ['px', 'em', 'rem'],
1964 'selectors' => [
1965 '{{WRAPPER}} .king-addons-testimonials-wall__empty' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1966 ],
1967 ]
1968 );
1969
1970 $this->add_control(
1971 'kng_tw_empty_align',
1972 [
1973 'label' => esc_html__('Text Alignment', 'king-addons'),
1974 'type' => Controls_Manager::SELECT,
1975 'options' => [
1976 'left' => esc_html__('Left', 'king-addons'),
1977 'center' => esc_html__('Center', 'king-addons'),
1978 'right' => esc_html__('Right', 'king-addons'),
1979 ],
1980 'default' => 'center',
1981 'selectors' => [
1982 '{{WRAPPER}} .king-addons-testimonials-wall__empty' => 'text-align: {{VALUE}};',
1983 ],
1984 ]
1985 );
1986
1987 $this->end_controls_section();
1988 }
1989
1990 /**
1991 * Skeleton style controls.
1992 *
1993 * @return void
1994 */
1995 protected function register_style_skeleton_controls(): void
1996 {
1997 $this->start_controls_section(
1998 'kng_tw_style_skeleton',
1999 [
2000 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Skeleton Loader', 'king-addons'),
2001 'tab' => Controls_Manager::TAB_STYLE,
2002 ]
2003 );
2004
2005 $this->add_control(
2006 'kng_tw_skeleton_base',
2007 [
2008 'label' => esc_html__('Base Color', 'king-addons'),
2009 'type' => Controls_Manager::COLOR,
2010 'selectors' => [
2011 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-skeleton-base: {{VALUE}};',
2012 ],
2013 ]
2014 );
2015
2016 $this->add_control(
2017 'kng_tw_skeleton_shine',
2018 [
2019 'label' => esc_html__('Shimmer Color', 'king-addons'),
2020 'type' => Controls_Manager::COLOR,
2021 'selectors' => [
2022 '{{WRAPPER}} .king-addons-testimonials-wall' => '--ka-tw-skeleton-shine: {{VALUE}};',
2023 ],
2024 ]
2025 );
2026
2027 $this->add_responsive_control(
2028 'kng_tw_skeleton_radius',
2029 [
2030 'label' => esc_html__('Skeleton Radius', 'king-addons'),
2031 'type' => Controls_Manager::SLIDER,
2032 'size_units' => ['px', '%'],
2033 'selectors' => [
2034 '{{WRAPPER}} .king-addons-testimonials-wall__skeleton-card' => 'border-radius: {{SIZE}}{{UNIT}};',
2035 ],
2036 ]
2037 );
2038
2039 $this->end_controls_section();
2040 }
2041
2042 /**
2043 * Render widget output.
2044 *
2045 * @return void
2046 */
2047 public function render(): void
2048 {
2049 $settings = $this->get_settings_for_display();
2050 $items = $settings['kng_tw_items'] ?? [];
2051
2052 if (empty($items)) {
2053 return;
2054 }
2055
2056 $is_editor = $this->is_elementor_editor_context();
2057 $max_editor_items = isset($settings['kng_tw_editor_max_items']) ? (int) $settings['kng_tw_editor_max_items'] : 8;
2058 if ($is_editor && $max_editor_items > 0) {
2059 $items = array_slice($items, 0, $max_editor_items);
2060 }
2061
2062 $layout = $settings['kng_tw_layout'] ?? 'masonry';
2063 $enable_filters = ($settings['kng_tw_enable_filters'] ?? 'yes') === 'yes';
2064 $filters_position = $settings['kng_tw_filters_position'] ?? 'top';
2065 $enable_category_filter = ($settings['kng_tw_enable_category_filter'] ?? 'yes') === 'yes';
2066 $enable_rating_filter = ($settings['kng_tw_enable_rating_filter'] ?? 'yes') === 'yes';
2067 $rating_ui = $settings['kng_tw_rating_ui'] ?? 'chips';
2068 $enable_search = ($settings['kng_tw_enable_search'] ?? 'yes') === 'yes';
2069 $search_placeholder = $settings['kng_tw_search_placeholder'] ?? esc_html__('Search testimonials', 'king-addons');
2070
2071 $enable_load_more = ($settings['kng_tw_enable_load_more'] ?? 'yes') === 'yes';
2072 $editor_load_more_preview = ($settings['kng_tw_editor_load_more_preview'] ?? '') === 'yes';
2073 $render_load_more = $enable_load_more && (!$is_editor || $editor_load_more_preview);
2074
2075 $initial_items = isset($settings['kng_tw_initial_items']) ? (int) $settings['kng_tw_initial_items'] : 9;
2076 $items_per_load = isset($settings['kng_tw_items_per_load']) ? (int) $settings['kng_tw_items_per_load'] : 6;
2077 $initial_items = max(1, $initial_items);
2078 $items_per_load = max(1, $items_per_load);
2079
2080 if (!$render_load_more) {
2081 $initial_items = count($items);
2082 }
2083
2084 $load_more_text = $settings['kng_tw_load_more_text'] ?? esc_html__('Load more', 'king-addons');
2085 $loading_text = $settings['kng_tw_loading_text'] ?? esc_html__('Loading...', 'king-addons');
2086
2087 $enable_skeleton = ($settings['kng_tw_enable_skeleton'] ?? 'yes') === 'yes';
2088 $skeleton_count = isset($settings['kng_tw_skeleton_count']) ? (int) $settings['kng_tw_skeleton_count'] : 3;
2089 $skeleton_count = max(1, $skeleton_count);
2090
2091 $enable_read_more = ($settings['kng_tw_enable_read_more'] ?? '') === 'yes';
2092 $read_more_lines = isset($settings['kng_tw_read_more_lines']) ? (int) $settings['kng_tw_read_more_lines'] : 4;
2093 $read_more_text = $settings['kng_tw_read_more_text'] ?? esc_html__('Read more', 'king-addons');
2094 $read_less_text = $settings['kng_tw_read_less_text'] ?? esc_html__('Read less', 'king-addons');
2095 $enable_empty_state = ($settings['kng_tw_enable_empty_state'] ?? 'yes') === 'yes';
2096 $empty_text = $settings['kng_tw_empty_text'] ?? esc_html__('No testimonials match your filters.', 'king-addons');
2097
2098 $show_avatar = ($settings['kng_tw_show_avatar'] ?? 'yes') === 'yes';
2099 $show_meta = ($settings['kng_tw_show_meta'] ?? 'yes') === 'yes';
2100 $show_rating = ($settings['kng_tw_show_rating'] ?? 'yes') === 'yes';
2101 $show_date = ($settings['kng_tw_show_date'] ?? '') === 'yes';
2102 $show_source = ($settings['kng_tw_show_source'] ?? 'yes') === 'yes';
2103
2104 $category_map = [];
2105 $item_categories = [];
2106 $has_rating = false;
2107
2108 foreach ($items as $index => $item) {
2109 $raw_categories = is_string($item['kng_tw_categories'] ?? '') ? $item['kng_tw_categories'] : '';
2110 $normalized = $this->normalize_categories($raw_categories);
2111 $item_categories[$index] = array_keys($normalized);
2112
2113 foreach ($normalized as $slug => $label) {
2114 if (!isset($category_map[$slug])) {
2115 $category_map[$slug] = $label;
2116 }
2117 }
2118
2119 $rating_value = $this->sanitize_rating($item['kng_tw_rating'] ?? 0);
2120 if ($rating_value > 0) {
2121 $has_rating = true;
2122 }
2123 }
2124
2125 if (!$has_rating) {
2126 $enable_rating_filter = false;
2127 }
2128
2129 $chip_style = $settings['kng_tw_chip_style'] ?? 'filled';
2130 $editor_masonry_preview = ($settings['kng_tw_editor_masonry_preview'] ?? '') === 'yes';
2131
2132 $wrapper_classes = [
2133 'king-addons-testimonials-wall',
2134 'king-addons-testimonials-wall--layout-' . sanitize_html_class((string) $layout),
2135 ];
2136
2137 if ($filters_position === 'left') {
2138 $wrapper_classes[] = 'king-addons-testimonials-wall--filters-left';
2139 }
2140
2141 if ($enable_read_more) {
2142 $wrapper_classes[] = 'king-addons-testimonials-wall--read-more';
2143 }
2144
2145 $wrapper_attributes = [
2146 'class' => implode(' ', $wrapper_classes),
2147 'data-layout' => $layout,
2148 'data-filters' => $enable_filters ? 'yes' : 'no',
2149 'data-filter-position' => $filters_position,
2150 'data-enable-category' => $enable_category_filter ? 'yes' : 'no',
2151 'data-enable-rating' => $enable_rating_filter ? 'yes' : 'no',
2152 'data-rating-ui' => $rating_ui,
2153 'data-enable-search' => $enable_search ? 'yes' : 'no',
2154 'data-enable-loadmore' => $render_load_more ? 'yes' : 'no',
2155 'data-initial' => (string) $initial_items,
2156 'data-per-load' => (string) $items_per_load,
2157 'data-read-more' => $enable_read_more ? 'yes' : 'no',
2158 'data-read-more-lines' => (string) max(2, $read_more_lines),
2159 'data-read-more-text' => $read_more_text,
2160 'data-read-less-text' => $read_less_text,
2161 'data-chip-style' => $chip_style,
2162 'data-editor-masonry' => $editor_masonry_preview ? 'yes' : 'no',
2163 'data-editor-loadmore' => $editor_load_more_preview ? 'yes' : 'no',
2164 'data-skeleton' => ($render_load_more && $enable_skeleton) ? 'yes' : 'no',
2165 'data-skeleton-count' => (string) $skeleton_count,
2166 ];
2167
2168 $auto_cols = isset($settings['kng_tw_item_min_width']['size']) && (float) $settings['kng_tw_item_min_width']['size'] > 0;
2169 if ($auto_cols) {
2170 $wrapper_attributes['data-auto-cols'] = 'yes';
2171 }
2172
2173 $all_label = $settings['kng_tw_all_label'] ?? esc_html__('All', 'king-addons');
2174 $date_format = get_option('date_format');
2175
2176 $filters_enabled = $enable_filters && ($enable_category_filter || $enable_rating_filter || $enable_search);
2177 ?>
2178 <div <?php echo $this->render_attribute_string($wrapper_attributes); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
2179 <div class="king-addons-testimonials-wall__inner">
2180 <div class="king-addons-testimonials-wall__layout">
2181 <?php if ($filters_enabled) : ?>
2182 <div class="king-addons-testimonials-wall__filters">
2183 <?php if ($enable_category_filter && !empty($category_map)) : ?>
2184 <div class="king-addons-testimonials-wall__filter-group king-addons-testimonials-wall__filter-group--categories" role="group" aria-label="<?php echo esc_attr__('Filter by category', 'king-addons'); ?>">
2185 <button type="button" class="king-addons-testimonials-wall__filter-chip is-active" data-filter="*" aria-pressed="true">
2186 <?php echo esc_html($all_label); ?>
2187 </button>
2188 <?php foreach ($category_map as $slug => $label) : ?>
2189 <button type="button" class="king-addons-testimonials-wall__filter-chip" data-filter="<?php echo esc_attr($slug); ?>" aria-pressed="false">
2190 <?php echo esc_html($label); ?>
2191 </button>
2192 <?php endforeach; ?>
2193 </div>
2194 <?php endif; ?>
2195
2196 <?php if ($enable_rating_filter) : ?>
2197 <div class="king-addons-testimonials-wall__filter-group king-addons-testimonials-wall__filter-group--rating" role="group" aria-label="<?php echo esc_attr__('Filter by rating', 'king-addons'); ?>">
2198 <?php if ($rating_ui === 'dropdown') : ?>
2199 <label class="screen-reader-text" for="king-addons-testimonials-wall-rating-<?php echo esc_attr($this->get_id()); ?>">
2200 <?php echo esc_html__('Rating filter', 'king-addons'); ?>
2201 </label>
2202 <select id="king-addons-testimonials-wall-rating-<?php echo esc_attr($this->get_id()); ?>" class="king-addons-testimonials-wall__rating-select">
2203 <option value="all"><?php echo esc_html__('All ratings', 'king-addons'); ?></option>
2204 <option value="5"><?php echo esc_html__('5', 'king-addons'); ?></option>
2205 <option value="4"><?php echo esc_html__('4+', 'king-addons'); ?></option>
2206 <option value="3"><?php echo esc_html__('3+', 'king-addons'); ?></option>
2207 </select>
2208 <?php else : ?>
2209 <button type="button" class="king-addons-testimonials-wall__filter-chip is-active" data-rating="all" aria-pressed="true">
2210 <?php echo esc_html($all_label); ?>
2211 </button>
2212 <button type="button" class="king-addons-testimonials-wall__filter-chip" data-rating="5" aria-pressed="false">
2213 <?php echo esc_html__('5', 'king-addons'); ?>
2214 </button>
2215 <button type="button" class="king-addons-testimonials-wall__filter-chip" data-rating="4" aria-pressed="false">
2216 <?php echo esc_html__('4+', 'king-addons'); ?>
2217 </button>
2218 <button type="button" class="king-addons-testimonials-wall__filter-chip" data-rating="3" aria-pressed="false">
2219 <?php echo esc_html__('3+', 'king-addons'); ?>
2220 </button>
2221 <?php endif; ?>
2222 </div>
2223 <?php endif; ?>
2224
2225 <?php if ($enable_search) : ?>
2226 <div class="king-addons-testimonials-wall__filter-group king-addons-testimonials-wall__filter-group--search">
2227 <input type="search" class="king-addons-testimonials-wall__search-input" placeholder="<?php echo esc_attr($search_placeholder); ?>" aria-label="<?php echo esc_attr($search_placeholder); ?>">
2228 </div>
2229 <?php endif; ?>
2230 </div>
2231 <?php endif; ?>
2232
2233 <div class="king-addons-testimonials-wall__grid" role="list">
2234 <?php foreach ($items as $index => $item) : ?>
2235 <?php
2236 $name = $item['kng_tw_name'] ?? '';
2237 $title = $item['kng_tw_title'] ?? '';
2238 $company = $item['kng_tw_company'] ?? '';
2239 $text = $item['kng_tw_text'] ?? '';
2240 $source = $item['kng_tw_source'] ?? '';
2241 $rating_value = $this->sanitize_rating($item['kng_tw_rating'] ?? 0);
2242 $date = $item['kng_tw_date'] ?? '';
2243 $meta_parts = array_filter([trim((string) $title), trim((string) $company)]);
2244 $meta = implode(' - ', $meta_parts);
2245
2246 $item_classes = [
2247 'king-addons-testimonials-wall__item',
2248 ];
2249
2250 if (!empty($item['_id'])) {
2251 $item_classes[] = 'elementor-repeater-item-' . sanitize_html_class((string) $item['_id']);
2252 }
2253
2254 if ($index < $initial_items) {
2255 $item_classes[] = 'is-visible';
2256 } else {
2257 $item_classes[] = 'is-hidden';
2258 }
2259
2260 $categories_list = $item_categories[$index] ?? [];
2261 $data_categories = !empty($categories_list) ? implode(' ', array_map('sanitize_html_class', $categories_list)) : '';
2262
2263 $search_blob = implode(' ', array_filter([
2264 $name,
2265 $title,
2266 $company,
2267 wp_strip_all_tags((string) $text),
2268 $source,
2269 implode(' ', $categories_list),
2270 ]));
2271
2272 $item_attributes = [
2273 'class' => implode(' ', $item_classes),
2274 'data-rating' => (string) $rating_value,
2275 'data-categories' => $data_categories,
2276 'data-search' => sanitize_text_field($search_blob),
2277 'role' => 'listitem',
2278 ];
2279 ?>
2280 <article <?php echo $this->render_attribute_string($item_attributes); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
2281 <div class="king-addons-testimonials-wall__card">
2282 <?php if ($show_avatar || !empty($name) || !empty($meta)) : ?>
2283 <div class="king-addons-testimonials-wall__header">
2284 <?php if ($show_avatar && !empty($item['kng_tw_avatar']['url'])) : ?>
2285 <div class="king-addons-testimonials-wall__avatar">
2286 <?php
2287 $image_settings = $item;
2288 if (isset($settings['kng_tw_avatar_size_size'])) {
2289 $image_settings['kng_tw_avatar_size_size'] = $settings['kng_tw_avatar_size_size'];
2290 }
2291 if (isset($settings['kng_tw_avatar_size_custom_dimension'])) {
2292 $image_settings['kng_tw_avatar_size_custom_dimension'] = $settings['kng_tw_avatar_size_custom_dimension'];
2293 }
2294 $avatar_html = Group_Control_Image_Size::get_attachment_image_html($image_settings, 'kng_tw_avatar_size', 'kng_tw_avatar');
2295 echo wp_kses_post($avatar_html);
2296 ?>
2297 </div>
2298 <?php endif; ?>
2299
2300 <div class="king-addons-testimonials-wall__author">
2301 <?php if (!empty($name)) : ?>
2302 <div class="king-addons-testimonials-wall__name"><?php echo esc_html($name); ?></div>
2303 <?php endif; ?>
2304 <?php if ($show_meta && !empty($meta)) : ?>
2305 <div class="king-addons-testimonials-wall__meta"><?php echo esc_html($meta); ?></div>
2306 <?php endif; ?>
2307 </div>
2308 </div>
2309 <?php endif; ?>
2310
2311 <?php if ($show_rating && $rating_value > 0) : ?>
2312 <div class="king-addons-testimonials-wall__rating" role="img" aria-label="<?php echo esc_attr(sprintf(esc_html__('Rated %1$s out of 5', 'king-addons'), $rating_value)); ?>">
2313 <?php $this->render_rating($rating_value); ?>
2314 </div>
2315 <?php endif; ?>
2316
2317 <div class="king-addons-testimonials-wall__text king-addons-testimonials-wall__searchable">
2318 <?php echo wp_kses_post($text); ?>
2319 </div>
2320
2321 <?php if ($enable_read_more) : ?>
2322 <button type="button" class="king-addons-testimonials-wall__read-more" aria-expanded="false">
2323 <?php echo esc_html($read_more_text); ?>
2324 </button>
2325 <?php endif; ?>
2326
2327 <?php if ($show_source || ($show_date && !empty($date))) : ?>
2328 <div class="king-addons-testimonials-wall__footer">
2329 <?php if ($show_source && !empty($source)) : ?>
2330 <span class="king-addons-testimonials-wall__source"><?php echo esc_html($source); ?></span>
2331 <?php endif; ?>
2332 <?php if ($show_date && !empty($date)) : ?>
2333 <span class="king-addons-testimonials-wall__date">
2334 <?php echo esc_html(date_i18n($date_format, strtotime($date))); ?>
2335 </span>
2336 <?php endif; ?>
2337 </div>
2338 <?php endif; ?>
2339 </div>
2340 </article>
2341 <?php endforeach; ?>
2342 </div>
2343
2344 <?php if ($enable_empty_state && !empty($empty_text)) : ?>
2345 <div class="king-addons-testimonials-wall__empty" role="status" aria-live="polite">
2346 <?php echo esc_html($empty_text); ?>
2347 </div>
2348 <?php endif; ?>
2349
2350 <?php if ($render_load_more) : ?>
2351 <div class="king-addons-testimonials-wall__load-more">
2352 <button type="button" class="king-addons-testimonials-wall__load-more-btn" data-load-text="<?php echo esc_attr($load_more_text); ?>" data-loading-text="<?php echo esc_attr($loading_text); ?>" aria-label="<?php echo esc_attr($load_more_text); ?>">
2353 <?php echo esc_html($load_more_text); ?>
2354 </button>
2355 </div>
2356 <?php endif; ?>
2357
2358 <?php if ($render_load_more && $enable_skeleton) : ?>
2359 <div class="king-addons-testimonials-wall__skeletons" aria-hidden="true">
2360 <?php for ($i = 0; $i < $skeleton_count; $i++) : ?>
2361 <div class="king-addons-testimonials-wall__skeleton-card"></div>
2362 <?php endfor; ?>
2363 </div>
2364 <?php endif; ?>
2365 </div>
2366 </div>
2367 </div>
2368 <?php
2369 }
2370
2371 /**
2372 * Render rating stars.
2373 *
2374 * @param int $rating Rating value.
2375 *
2376 * @return void
2377 */
2378 protected function render_rating(int $rating): void
2379 {
2380 $rating = max(0, min(5, $rating));
2381 ?>
2382 <div class="king-addons-testimonials-wall__stars" aria-hidden="true">
2383 <?php for ($i = 1; $i <= 5; $i++) : ?>
2384 <span class="king-addons-testimonials-wall__star<?php echo ($i <= $rating) ? ' is-filled' : ''; ?>">
2385 <svg viewBox="0 0 24 24" focusable="false" aria-hidden="true">
2386 <path d="M12 2l2.9 6.1 6.7.9-4.9 4.8 1.2 6.7L12 17.8l-5.9 3.1 1.2-6.7-4.9-4.8 6.7-.9L12 2z"/>
2387 </svg>
2388 </span>
2389 <?php endfor; ?>
2390 </div>
2391 <?php
2392 }
2393
2394 /**
2395 * Register Pro notice controls.
2396 *
2397 * @return void
2398 */
2399 protected function register_pro_notice_controls(): void
2400 {
2401 if (!king_addons_can_use_pro()) {
2402 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'testimonials-wall', [
2403 'CPT and WooCommerce review sources with ACF mapping',
2404 'Sorting: newest, highest rating, featured first',
2405 'Featured highlight with badge and 2x masonry cards',
2406 'Schema.org Review & AggregateRating JSON-LD',
2407 'AJAX filtering, infinite scroll, and caching layer',
2408 ]);
2409 }
2410 }
2411
2412 /**
2413 * Normalize category list into slug => label pairs.
2414 *
2415 * @param string $raw Raw category string.
2416 *
2417 * @return array<string, string>
2418 */
2419 protected function normalize_categories(string $raw): array
2420 {
2421 $labels = preg_split('/[,;]+/', $raw) ?: [];
2422 $normalized = [];
2423
2424 foreach ($labels as $label) {
2425 $label = trim($label);
2426 if ($label === '') {
2427 continue;
2428 }
2429 $slug = sanitize_title($label);
2430 if ($slug === '') {
2431 continue;
2432 }
2433 if (!isset($normalized[$slug])) {
2434 $normalized[$slug] = $label;
2435 }
2436 }
2437
2438 return $normalized;
2439 }
2440
2441 /**
2442 * Sanitize rating value.
2443 *
2444 * @param mixed $value Raw rating value.
2445 *
2446 * @return int
2447 */
2448 protected function sanitize_rating($value): int
2449 {
2450 $rating = is_numeric($value) ? (int) $value : 0;
2451 return max(0, min(5, $rating));
2452 }
2453
2454 /**
2455 * Render HTML attributes from array.
2456 *
2457 * @param array<string, string> $attributes Attributes list.
2458 *
2459 * @return string
2460 */
2461 protected function render_attribute_string(array $attributes): string
2462 {
2463 $pairs = [];
2464 foreach ($attributes as $key => $value) {
2465 if ($value === '' || $value === null) {
2466 continue;
2467 }
2468 $pairs[] = sprintf('%s="%s"', $key, esc_attr($value));
2469 }
2470
2471 return implode(' ', $pairs);
2472 }
2473
2474 /**
2475 * Detect if Elementor editor/preview is active.
2476 *
2477 * @return bool
2478 */
2479 protected function is_elementor_editor_context(): bool
2480 {
2481 if (isset($_GET['elementor-preview'])) {
2482 return true;
2483 }
2484
2485 if (!defined('ELEMENTOR_VERSION') || !class_exists(Plugin::class)) {
2486 return false;
2487 }
2488
2489 $plugin = Plugin::$instance;
2490
2491 if (isset($plugin->editor) && is_object($plugin->editor) && method_exists($plugin->editor, 'is_edit_mode')) {
2492 if ($plugin->editor->is_edit_mode()) {
2493 return true;
2494 }
2495 }
2496
2497 if (isset($plugin->preview) && is_object($plugin->preview) && method_exists($plugin->preview, 'is_preview_mode')) {
2498 if ($plugin->preview->is_preview_mode()) {
2499 return true;
2500 }
2501 }
2502
2503 return false;
2504 }
2505 }
2506