PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
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 / Comparison_Matrix_Cards / Comparison_Matrix_Cards.php

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

3,653 lines 125.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Comparison Matrix Cards 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_Typography;
15 use Elementor\Plugin;
16 use Elementor\Repeater;
17 use Elementor\Widget_Base;
18 use WC_Product;
19
20 if (!defined('ABSPATH')) {
21 exit;
22 }
23
24 /**
25 * Renders a comparison matrix in card layout.
26 */
27 class Comparison_Matrix_Cards extends Widget_Base
28 {
29
30 /**
31 * Widget slug.
32 */
33 public function get_name(): string
34 {
35 return 'king-addons-comparison-matrix-cards';
36 }
37
38 /**
39 * Widget title.
40 */
41 public function get_title(): string
42 {
43 return esc_html__('Comparison Matrix Cards', 'king-addons');
44 }
45
46 /**
47 * Widget icon.
48 */
49 public function get_icon(): string
50 {
51 return 'eicon-price-table';
52 }
53
54 /**
55 * Style dependencies.
56 *
57 * @return array<int, string>
58 */
59 public function get_style_depends(): array
60 {
61 return [
62 KING_ADDONS_ASSETS_UNIQUE_KEY . '-comparison-matrix-cards-style',
63 ];
64 }
65
66 /**
67 * Script dependencies.
68 *
69 * @return array<int, string>
70 */
71 public function get_script_depends(): array
72 {
73 return [
74 KING_ADDONS_ASSETS_UNIQUE_KEY . '-comparison-matrix-cards-script',
75 ];
76 }
77
78 /**
79 * Categories.
80 *
81 * @return array<int, string>
82 */
83 public function get_categories(): array
84 {
85 return ['king-addons'];
86 }
87
88 /**
89 * Keywords.
90 *
91 * @return array<int, string>
92 */
93 public function get_keywords(): array
94 {
95 return ['comparison', 'matrix', 'cards', 'pricing', 'plans'];
96 }
97
98 public function get_custom_help_url()
99 {
100 return 'mailto:bug@kingaddons.com?subject=Bug Report - King Addons&body=Please describe the issue';
101 }
102
103 /**
104 * Register widget controls.
105 */
106 public function register_controls(): void
107 {
108 $this->register_data_source_controls();
109 $this->register_plan_controls();
110 $this->register_feature_controls();
111 $this->register_layout_controls();
112 $this->register_highlight_controls();
113 $this->register_sticky_controls();
114 $this->register_search_controls();
115 $this->register_style_card_controls();
116 $this->register_style_header_controls();
117 $this->register_style_feature_controls();
118 $this->register_style_button_controls();
119 $this->register_style_highlight_controls();
120 $this->register_style_sticky_controls();
121 $this->register_style_search_controls();
122 $this->register_pro_notice_controls();
123 }
124
125 /**
126 * Render widget output.
127 */
128 public function render(): void
129 {
130 $settings = $this->get_settings_for_display();
131 $data = $this->get_render_data($settings);
132 if (!empty($data['pro_locked'])) {
133 if ($this->is_editor_mode()) {
134 echo '<div class="king-addons-pro-notice">' . esc_html__('Upgrade to Pro to use ACF/WooCommerce data sources and advanced comparison tools.', 'king-addons') . '</div>';
135 }
136 return;
137 }
138
139 $plans = $data['plans'] ?? [];
140 $features = $data['features'] ?? [];
141
142 if (count($plans) < 2) {
143 return;
144 }
145
146 $context = $this->get_render_context($settings, count($plans));
147 $wrapper_classes = $this->get_wrapper_classes($settings, $context);
148 $wrapper_attributes = $this->get_wrapper_attributes($settings, $context);
149
150 ?>
151 <div class="<?php echo esc_attr(implode(' ', $wrapper_classes)); ?>"<?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
152 <?php $this->render_controls_bar($context); ?>
153 <div class="king-addons-comparison-matrix-cards__grid">
154 <?php foreach ($plans as $index => $plan) : ?>
155 <?php $this->render_card($plan, $features, $index); ?>
156 <?php endforeach; ?>
157 </div>
158 </div>
159 <?php
160 }
161
162 /**
163 * Register data source controls.
164 */
165 protected function register_data_source_controls(): void
166 {
167 $this->start_controls_section(
168 'kng_data_source_section',
169 [
170 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Data Source', 'king-addons'),
171 'tab' => Controls_Manager::TAB_CONTENT,
172 ]
173 );
174
175 $this->add_control(
176 'kng_data_source',
177 [
178 'label' => esc_html__('Source', 'king-addons'),
179 'type' => Controls_Manager::SELECT,
180 'default' => 'manual',
181 'options' => [
182 'manual' => esc_html__('Manual', 'king-addons'),
183 'acf' => $this->get_pro_label(esc_html__('ACF', 'king-addons')),
184 'woo' => $this->get_pro_label(esc_html__('WooCommerce', 'king-addons')),
185 ],
186 ]
187 );
188
189 $this->add_control(
190 'kng_max_plans',
191 [
192 'label' => $this->get_pro_label(esc_html__('Max Plans', 'king-addons')),
193 'type' => Controls_Manager::NUMBER,
194 'default' => 4,
195 'min' => 2,
196 'max' => 8,
197 'step' => 1,
198 'classes' => $this->get_pro_control_class(),
199 'condition' => [
200 'kng_data_source!' => 'manual',
201 ],
202 'description' => esc_html__('Applies to ACF/Woo data sources.', 'king-addons'),
203 ]
204 );
205
206 $this->end_controls_section();
207
208 $this->register_acf_controls();
209 $this->register_woo_controls();
210 }
211
212 /**
213 * Register ACF data controls.
214 */
215 protected function register_acf_controls(): void
216 {
217 $this->start_controls_section(
218 'kng_acf_section',
219 [
220 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('ACF Mapping', 'king-addons'),
221 'tab' => Controls_Manager::TAB_CONTENT,
222 'condition' => [
223 'kng_data_source' => 'acf',
224 ],
225 ]
226 );
227
228 if (!function_exists('get_field')) {
229 $this->add_control(
230 'kng_acf_missing_notice',
231 [
232 'type' => Controls_Manager::RAW_HTML,
233 'raw' => esc_html__('ACF plugin is not active. Enable ACF to use this data source.', 'king-addons'),
234 'content_classes' => 'king-addons-pro-notice',
235 ]
236 );
237 }
238
239 $this->add_control(
240 'kng_acf_post_source',
241 [
242 'label' => $this->get_pro_label(esc_html__('Post ID Source', 'king-addons')),
243 'type' => Controls_Manager::SELECT,
244 'default' => 'current',
245 'options' => [
246 'current' => esc_html__('Current Post', 'king-addons'),
247 'custom' => esc_html__('Custom Post ID', 'king-addons'),
248 ],
249 'classes' => $this->get_pro_control_class(),
250 ]
251 );
252
253 $this->add_control(
254 'kng_acf_post_id',
255 [
256 'label' => $this->get_pro_label(esc_html__('Custom Post ID', 'king-addons')),
257 'type' => Controls_Manager::NUMBER,
258 'min' => 1,
259 'classes' => $this->get_pro_control_class(),
260 'condition' => [
261 'kng_acf_post_source' => 'custom',
262 ],
263 ]
264 );
265
266 $this->add_control(
267 'kng_acf_plans_repeater',
268 [
269 'label' => $this->get_pro_label(esc_html__('Plans Repeater Field', 'king-addons')),
270 'type' => Controls_Manager::TEXT,
271 'placeholder' => 'plans',
272 'classes' => $this->get_pro_control_class(),
273 ]
274 );
275
276 $this->add_control(
277 'kng_acf_plan_name_field',
278 [
279 'label' => $this->get_pro_label(esc_html__('Plan Name Field', 'king-addons')),
280 'type' => Controls_Manager::TEXT,
281 'placeholder' => 'plan_name',
282 'classes' => $this->get_pro_control_class(),
283 ]
284 );
285
286 $this->add_control(
287 'kng_acf_plan_badge_field',
288 [
289 'label' => $this->get_pro_label(esc_html__('Badge Field', 'king-addons')),
290 'type' => Controls_Manager::TEXT,
291 'placeholder' => 'plan_badge',
292 'classes' => $this->get_pro_control_class(),
293 ]
294 );
295
296 $this->add_control(
297 'kng_acf_plan_description_field',
298 [
299 'label' => $this->get_pro_label(esc_html__('Description Field', 'king-addons')),
300 'type' => Controls_Manager::TEXT,
301 'placeholder' => 'plan_description',
302 'classes' => $this->get_pro_control_class(),
303 ]
304 );
305
306 $this->add_control(
307 'kng_acf_plan_price_field',
308 [
309 'label' => $this->get_pro_label(esc_html__('Price Field', 'king-addons')),
310 'type' => Controls_Manager::TEXT,
311 'placeholder' => 'plan_price',
312 'classes' => $this->get_pro_control_class(),
313 ]
314 );
315
316 $this->add_control(
317 'kng_acf_plan_subtitle_field',
318 [
319 'label' => $this->get_pro_label(esc_html__('Subtitle Field', 'king-addons')),
320 'type' => Controls_Manager::TEXT,
321 'placeholder' => 'plan_subtitle',
322 'classes' => $this->get_pro_control_class(),
323 ]
324 );
325
326 $this->add_control(
327 'kng_acf_plan_cta_label_field',
328 [
329 'label' => $this->get_pro_label(esc_html__('CTA Label Field', 'king-addons')),
330 'type' => Controls_Manager::TEXT,
331 'placeholder' => 'cta_label',
332 'classes' => $this->get_pro_control_class(),
333 ]
334 );
335
336 $this->add_control(
337 'kng_acf_plan_cta_link_field',
338 [
339 'label' => $this->get_pro_label(esc_html__('CTA Link Field', 'king-addons')),
340 'type' => Controls_Manager::TEXT,
341 'placeholder' => 'cta_link',
342 'classes' => $this->get_pro_control_class(),
343 'description' => esc_html__('Accepts ACF Link field or URL string.', 'king-addons'),
344 ]
345 );
346
347 $this->add_control(
348 'kng_acf_plan_footer_field',
349 [
350 'label' => $this->get_pro_label(esc_html__('Footer Note Field', 'king-addons')),
351 'type' => Controls_Manager::TEXT,
352 'placeholder' => 'plan_footer',
353 'classes' => $this->get_pro_control_class(),
354 ]
355 );
356
357 $this->add_control(
358 'kng_acf_plan_features_repeater',
359 [
360 'label' => $this->get_pro_label(esc_html__('Plan Features Repeater', 'king-addons')),
361 'type' => Controls_Manager::TEXT,
362 'placeholder' => 'features',
363 'classes' => $this->get_pro_control_class(),
364 'separator' => 'before',
365 ]
366 );
367
368 $this->add_control(
369 'kng_acf_feature_label_field',
370 [
371 'label' => $this->get_pro_label(esc_html__('Feature Label Field', 'king-addons')),
372 'type' => Controls_Manager::TEXT,
373 'placeholder' => 'feature_label',
374 'classes' => $this->get_pro_control_class(),
375 ]
376 );
377
378 $this->add_control(
379 'kng_acf_feature_value_field',
380 [
381 'label' => $this->get_pro_label(esc_html__('Feature Value Field', 'king-addons')),
382 'type' => Controls_Manager::TEXT,
383 'placeholder' => 'feature_value',
384 'classes' => $this->get_pro_control_class(),
385 ]
386 );
387
388 $this->add_control(
389 'kng_acf_feature_value_type_field',
390 [
391 'label' => $this->get_pro_label(esc_html__('Feature Value Type Field', 'king-addons')),
392 'type' => Controls_Manager::TEXT,
393 'placeholder' => 'feature_type',
394 'classes' => $this->get_pro_control_class(),
395 'description' => esc_html__('Optional field with values: included, excluded, limited, text.', 'king-addons'),
396 ]
397 );
398
399 $this->add_control(
400 'kng_acf_feature_tooltip_field',
401 [
402 'label' => $this->get_pro_label(esc_html__('Tooltip Field', 'king-addons')),
403 'type' => Controls_Manager::TEXT,
404 'placeholder' => 'feature_tooltip',
405 'classes' => $this->get_pro_control_class(),
406 ]
407 );
408
409 $this->add_control(
410 'kng_acf_feature_group_field',
411 [
412 'label' => $this->get_pro_label(esc_html__('Group Field', 'king-addons')),
413 'type' => Controls_Manager::TEXT,
414 'placeholder' => 'feature_group',
415 'classes' => $this->get_pro_control_class(),
416 ]
417 );
418
419 $this->end_controls_section();
420 }
421
422 /**
423 * Register WooCommerce data controls.
424 */
425 protected function register_woo_controls(): void
426 {
427 $this->start_controls_section(
428 'kng_woo_section',
429 [
430 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('WooCommerce Mapping', 'king-addons'),
431 'tab' => Controls_Manager::TAB_CONTENT,
432 'condition' => [
433 'kng_data_source' => 'woo',
434 ],
435 ]
436 );
437
438 if (!class_exists('WooCommerce')) {
439 $this->add_control(
440 'kng_woo_missing_notice',
441 [
442 'type' => Controls_Manager::RAW_HTML,
443 'raw' => esc_html__('WooCommerce plugin is not active. Enable WooCommerce to use this data source.', 'king-addons'),
444 'content_classes' => 'king-addons-pro-notice',
445 ]
446 );
447 }
448
449 $this->add_control(
450 'kng_woo_products_source',
451 [
452 'label' => $this->get_pro_label(esc_html__('Products Source', 'king-addons')),
453 'type' => Controls_Manager::SELECT,
454 'default' => 'manual',
455 'options' => [
456 'manual' => esc_html__('Manual Select', 'king-addons'),
457 'related' => esc_html__('Related Products', 'king-addons'),
458 'upsell' => esc_html__('Upsells', 'king-addons'),
459 'cross_sell' => esc_html__('Cross-sells', 'king-addons'),
460 ],
461 'classes' => $this->get_pro_control_class(),
462 ]
463 );
464
465 $this->add_control(
466 'kng_woo_products',
467 [
468 'label' => $this->get_pro_label(esc_html__('Select Products', 'king-addons')),
469 'type' => 'king-addons-ajax-select2',
470 'options' => 'ajaxselect2/getPostsByPostType',
471 'query_slug' => 'product',
472 'multiple' => true,
473 'label_block' => true,
474 'classes' => $this->get_pro_control_class(),
475 'condition' => [
476 'kng_woo_products_source' => 'manual',
477 ],
478 ]
479 );
480
481 $this->add_control(
482 'kng_woo_products_limit',
483 [
484 'label' => $this->get_pro_label(esc_html__('Max Products', 'king-addons')),
485 'type' => Controls_Manager::NUMBER,
486 'default' => 4,
487 'min' => 2,
488 'max' => 8,
489 'step' => 1,
490 'classes' => $this->get_pro_control_class(),
491 ]
492 );
493
494 $this->add_control(
495 'kng_woo_compare_variations',
496 [
497 'label' => $this->get_pro_label(esc_html__('Compare Variations', 'king-addons')),
498 'type' => Controls_Manager::SWITCHER,
499 'return_value' => 'yes',
500 'default' => '',
501 'classes' => $this->get_pro_control_class(),
502 'condition' => [
503 'kng_woo_products_source' => 'manual',
504 ],
505 'description' => esc_html__('Compare variations of a single variable product.', 'king-addons'),
506 ]
507 );
508
509 $this->add_control(
510 'kng_woo_variation_title_mode',
511 [
512 'label' => $this->get_pro_label(esc_html__('Variation Title', 'king-addons')),
513 'type' => Controls_Manager::SELECT,
514 'default' => 'full',
515 'options' => [
516 'full' => esc_html__('Full Name', 'king-addons'),
517 'attributes' => esc_html__('Attributes Only', 'king-addons'),
518 'parent' => esc_html__('Parent Name', 'king-addons'),
519 ],
520 'classes' => $this->get_pro_control_class(),
521 'condition' => [
522 'kng_woo_compare_variations' => 'yes',
523 ],
524 ]
525 );
526
527 $this->add_control(
528 'kng_woo_variation_subtitle_mode',
529 [
530 'label' => $this->get_pro_label(esc_html__('Variation Subtitle', 'king-addons')),
531 'type' => Controls_Manager::SELECT,
532 'default' => 'none',
533 'options' => [
534 'none' => esc_html__('None', 'king-addons'),
535 'attributes' => esc_html__('Attributes', 'king-addons'),
536 ],
537 'classes' => $this->get_pro_control_class(),
538 'condition' => [
539 'kng_woo_compare_variations' => 'yes',
540 ],
541 ]
542 );
543
544 $this->add_control(
545 'kng_woo_variation_notice',
546 [
547 'type' => Controls_Manager::RAW_HTML,
548 'raw' => esc_html__('When enabled, only the first selected product is used to load variations.', 'king-addons'),
549 'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
550 'condition' => [
551 'kng_woo_compare_variations' => 'yes',
552 ],
553 ]
554 );
555
556 $this->add_control(
557 'kng_woo_show_price',
558 [
559 'label' => $this->get_pro_label(esc_html__('Show Price', 'king-addons')),
560 'type' => Controls_Manager::SWITCHER,
561 'return_value' => 'yes',
562 'default' => 'yes',
563 'classes' => $this->get_pro_control_class(),
564 ]
565 );
566
567 $this->add_control(
568 'kng_woo_cta_mode',
569 [
570 'label' => $this->get_pro_label(esc_html__('CTA Type', 'king-addons')),
571 'type' => Controls_Manager::SELECT,
572 'default' => 'product',
573 'options' => [
574 'product' => esc_html__('Product Page', 'king-addons'),
575 'add_to_cart' => esc_html__('Add to Cart', 'king-addons'),
576 ],
577 'classes' => $this->get_pro_control_class(),
578 ]
579 );
580
581 $this->add_control(
582 'kng_woo_cta_label',
583 [
584 'label' => $this->get_pro_label(esc_html__('CTA Label', 'king-addons')),
585 'type' => Controls_Manager::TEXT,
586 'default' => esc_html__('View Product', 'king-addons'),
587 'classes' => $this->get_pro_control_class(),
588 ]
589 );
590
591 $attributes = new Repeater();
592 $attributes->add_control(
593 'kng_woo_attr_slug',
594 [
595 'label' => esc_html__('Attribute Slug', 'king-addons'),
596 'type' => Controls_Manager::TEXT,
597 'placeholder' => 'pa_color',
598 'label_block' => true,
599 ]
600 );
601 $attributes->add_control(
602 'kng_woo_attr_label',
603 [
604 'label' => esc_html__('Label Override', 'king-addons'),
605 'type' => Controls_Manager::TEXT,
606 'placeholder' => esc_html__('Color', 'king-addons'),
607 ]
608 );
609 $attributes->add_control(
610 'kng_woo_attr_group',
611 [
612 'label' => esc_html__('Group', 'king-addons'),
613 'type' => Controls_Manager::TEXT,
614 'placeholder' => esc_html__('Performance', 'king-addons'),
615 ]
616 );
617 $attributes->add_control(
618 'kng_woo_attr_tooltip',
619 [
620 'label' => esc_html__('Tooltip', 'king-addons'),
621 'type' => Controls_Manager::TEXTAREA,
622 'rows' => 2,
623 ]
624 );
625 $attributes->add_control(
626 'kng_woo_attr_fallback',
627 [
628 'label' => esc_html__('Fallback Meta Key', 'king-addons'),
629 'type' => Controls_Manager::TEXT,
630 'placeholder' => 'custom_feature',
631 ]
632 );
633
634 $this->add_control(
635 'kng_woo_attributes',
636 [
637 'label' => $this->get_pro_label(esc_html__('Compare Attributes', 'king-addons')),
638 'type' => Controls_Manager::REPEATER,
639 'fields' => $attributes->get_controls(),
640 'title_field' => '{{ kng_woo_attr_slug }}',
641 'classes' => $this->get_pro_control_class(),
642 ]
643 );
644
645 $this->end_controls_section();
646 }
647
648 /**
649 * Register manual plan controls.
650 */
651 protected function register_plan_controls(): void
652 {
653 $this->start_controls_section(
654 'kng_plans_section',
655 [
656 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Plans', 'king-addons'),
657 'tab' => Controls_Manager::TAB_CONTENT,
658 'condition' => [
659 'kng_data_source' => 'manual',
660 ],
661 ]
662 );
663
664 $plan_options = [
665 '2' => esc_html__('2 Plans', 'king-addons'),
666 '3' => esc_html__('3 Plans', 'king-addons'),
667 ];
668
669 if ($this->can_use_pro()) {
670 $plan_options['4'] = esc_html__('4 Plans', 'king-addons');
671 $plan_options['5'] = esc_html__('5 Plans', 'king-addons');
672 $plan_options['6'] = esc_html__('6 Plans', 'king-addons');
673 } else {
674 $plan_options['4'] = $this->get_pro_label(esc_html__('4 Plans', 'king-addons'));
675 $plan_options['5'] = $this->get_pro_label(esc_html__('5 Plans', 'king-addons'));
676 $plan_options['6'] = $this->get_pro_label(esc_html__('6 Plans', 'king-addons'));
677 }
678
679 $this->add_control(
680 'kng_plans_count',
681 [
682 'label' => esc_html__('Plans Count', 'king-addons'),
683 'type' => Controls_Manager::SELECT,
684 'default' => '3',
685 'options' => $plan_options,
686 'description' => esc_html__('Free version supports 2-3 plans.', 'king-addons'),
687 ]
688 );
689
690 $repeater = new Repeater();
691
692 $repeater->add_control(
693 'kng_plan_name',
694 [
695 'label' => esc_html__('Name', 'king-addons'),
696 'type' => Controls_Manager::TEXT,
697 'default' => esc_html__('Plan name', 'king-addons'),
698 'label_block' => true,
699 'dynamic' => [
700 'active' => true,
701 ],
702 ]
703 );
704
705 $repeater->add_control(
706 'kng_plan_badge',
707 [
708 'label' => esc_html__('Badge', 'king-addons'),
709 'type' => Controls_Manager::TEXT,
710 'default' => '',
711 'placeholder' => esc_html__('Most Popular', 'king-addons'),
712 'dynamic' => [
713 'active' => true,
714 ],
715 ]
716 );
717
718 $repeater->add_control(
719 'kng_plan_description',
720 [
721 'label' => esc_html__('Short Description', 'king-addons'),
722 'type' => Controls_Manager::TEXTAREA,
723 'rows' => 2,
724 'default' => '',
725 'dynamic' => [
726 'active' => true,
727 ],
728 ]
729 );
730
731 $repeater->add_control(
732 'kng_plan_price',
733 [
734 'label' => esc_html__('Price', 'king-addons'),
735 'type' => Controls_Manager::TEXT,
736 'default' => '$29',
737 'dynamic' => [
738 'active' => true,
739 ],
740 ]
741 );
742
743 $repeater->add_control(
744 'kng_plan_subtitle',
745 [
746 'label' => esc_html__('Subtitle', 'king-addons'),
747 'type' => Controls_Manager::TEXT,
748 'default' => esc_html__('per month', 'king-addons'),
749 'dynamic' => [
750 'active' => true,
751 ],
752 ]
753 );
754
755 $repeater->add_control(
756 'kng_plan_cta_label',
757 [
758 'label' => esc_html__('CTA Label', 'king-addons'),
759 'type' => Controls_Manager::TEXT,
760 'default' => esc_html__('Get started', 'king-addons'),
761 'dynamic' => [
762 'active' => true,
763 ],
764 ]
765 );
766
767 $repeater->add_control(
768 'kng_plan_cta_link',
769 [
770 'label' => esc_html__('CTA Link', 'king-addons'),
771 'type' => Controls_Manager::URL,
772 'placeholder' => esc_html__('https://your-link.com', 'king-addons'),
773 'default' => [
774 'url' => '',
775 'is_external' => false,
776 'nofollow' => false,
777 ],
778 ]
779 );
780
781 $repeater->add_control(
782 'kng_plan_footer_note',
783 [
784 'label' => esc_html__('Footer Note', 'king-addons'),
785 'type' => Controls_Manager::TEXT,
786 'default' => '',
787 'dynamic' => [
788 'active' => true,
789 ],
790 ]
791 );
792
793 $this->add_control(
794 'kng_plans',
795 [
796 'label' => esc_html__('Plans', 'king-addons'),
797 'type' => Controls_Manager::REPEATER,
798 'fields' => $repeater->get_controls(),
799 'title_field' => '{{ kng_plan_name }}',
800 'default' => [
801 [
802 'kng_plan_name' => esc_html__('Starter', 'king-addons'),
803 'kng_plan_badge' => '',
804 'kng_plan_price' => '$19',
805 'kng_plan_subtitle' => esc_html__('per month', 'king-addons'),
806 'kng_plan_cta_label' => esc_html__('Choose Starter', 'king-addons'),
807 ],
808 [
809 'kng_plan_name' => esc_html__('Pro', 'king-addons'),
810 'kng_plan_badge' => esc_html__('Most Popular', 'king-addons'),
811 'kng_plan_price' => '$39',
812 'kng_plan_subtitle' => esc_html__('per month', 'king-addons'),
813 'kng_plan_cta_label' => esc_html__('Choose Pro', 'king-addons'),
814 ],
815 [
816 'kng_plan_name' => esc_html__('Business', 'king-addons'),
817 'kng_plan_badge' => '',
818 'kng_plan_price' => '$79',
819 'kng_plan_subtitle' => esc_html__('per month', 'king-addons'),
820 'kng_plan_cta_label' => esc_html__('Choose Business', 'king-addons'),
821 ],
822 ],
823 ]
824 );
825
826 $this->end_controls_section();
827 }
828
829 /**
830 * Register feature controls (manual mode).
831 */
832 protected function register_feature_controls(): void
833 {
834 $this->start_controls_section(
835 'kng_features_section',
836 [
837 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Features', 'king-addons'),
838 'tab' => Controls_Manager::TAB_CONTENT,
839 'condition' => [
840 'kng_data_source' => 'manual',
841 ],
842 ]
843 );
844
845 $repeater = new Repeater();
846
847 $repeater->add_control(
848 'kng_feature_label',
849 [
850 'label' => esc_html__('Feature Label', 'king-addons'),
851 'type' => Controls_Manager::TEXT,
852 'default' => esc_html__('Feature name', 'king-addons'),
853 'label_block' => true,
854 'dynamic' => [
855 'active' => true,
856 ],
857 ]
858 );
859
860 $repeater->add_control(
861 'kng_feature_tooltip',
862 [
863 'label' => esc_html__('Tooltip', 'king-addons'),
864 'type' => Controls_Manager::TEXTAREA,
865 'rows' => 2,
866 'default' => '',
867 'dynamic' => [
868 'active' => true,
869 ],
870 ]
871 );
872
873 $repeater->add_control(
874 'kng_feature_group',
875 [
876 'label' => esc_html__('Group', 'king-addons'),
877 'type' => Controls_Manager::TEXT,
878 'default' => '',
879 'placeholder' => esc_html__('Security', 'king-addons'),
880 'dynamic' => [
881 'active' => true,
882 ],
883 ]
884 );
885
886 $plan_conditions = [
887 3 => ['kng_plans_count' => ['3', '4', '5', '6']],
888 4 => ['kng_plans_count' => ['4', '5', '6']],
889 5 => ['kng_plans_count' => ['5', '6']],
890 6 => ['kng_plans_count' => ['6']],
891 ];
892
893 $this->add_feature_value_controls($repeater, 1, esc_html__('Plan 1', 'king-addons'));
894 $this->add_feature_value_controls($repeater, 2, esc_html__('Plan 2', 'king-addons'));
895 $this->add_feature_value_controls($repeater, 3, esc_html__('Plan 3', 'king-addons'), $plan_conditions[3]);
896 $this->add_feature_value_controls($repeater, 4, $this->get_pro_label(esc_html__('Plan 4', 'king-addons')), $plan_conditions[4], $this->get_pro_control_class());
897 $this->add_feature_value_controls($repeater, 5, $this->get_pro_label(esc_html__('Plan 5', 'king-addons')), $plan_conditions[5], $this->get_pro_control_class());
898 $this->add_feature_value_controls($repeater, 6, $this->get_pro_label(esc_html__('Plan 6', 'king-addons')), $plan_conditions[6], $this->get_pro_control_class());
899
900 $this->add_control(
901 'kng_features',
902 [
903 'label' => esc_html__('Feature Rows', 'king-addons'),
904 'type' => Controls_Manager::REPEATER,
905 'fields' => $repeater->get_controls(),
906 'title_field' => '{{ kng_feature_label }}',
907 'default' => [
908 [
909 'kng_feature_label' => esc_html__('Unlimited projects', 'king-addons'),
910 'kng_feature_value_1_type' => 'limited',
911 'kng_feature_value_2_type' => 'included',
912 'kng_feature_value_3_type' => 'included',
913 ],
914 [
915 'kng_feature_label' => esc_html__('Team seats', 'king-addons'),
916 'kng_feature_value_1_type' => 'text',
917 'kng_feature_value_1_text' => '5',
918 'kng_feature_value_2_type' => 'text',
919 'kng_feature_value_2_text' => '20',
920 'kng_feature_value_3_type' => 'text',
921 'kng_feature_value_3_text' => 'Unlimited',
922 ],
923 [
924 'kng_feature_label' => esc_html__('Priority support', 'king-addons'),
925 'kng_feature_value_1_type' => 'excluded',
926 'kng_feature_value_2_type' => 'included',
927 'kng_feature_value_3_type' => 'included',
928 ],
929 [
930 'kng_feature_label' => esc_html__('SLA guarantee', 'king-addons'),
931 'kng_feature_value_1_type' => 'excluded',
932 'kng_feature_value_2_type' => 'limited',
933 'kng_feature_value_3_type' => 'included',
934 ],
935 ],
936 ]
937 );
938
939 $this->end_controls_section();
940 }
941
942 /**
943 * Register layout controls.
944 */
945 protected function register_layout_controls(): void
946 {
947 $this->start_controls_section(
948 'kng_layout_section',
949 [
950 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
951 'tab' => Controls_Manager::TAB_CONTENT,
952 ]
953 );
954
955 $this->add_control(
956 'kng_preset',
957 [
958 'label' => esc_html__('Preset', 'king-addons'),
959 'type' => Controls_Manager::SELECT,
960 'default' => 'minimal',
961 'options' => [
962 'minimal' => esc_html__('Minimal', 'king-addons'),
963 'bordered' => esc_html__('Bordered', 'king-addons'),
964 'soft' => esc_html__('Soft Shadow', 'king-addons'),
965 ],
966 ]
967 );
968
969 $column_options = [
970 '2' => esc_html__('2 Columns', 'king-addons'),
971 '3' => esc_html__('3 Columns', 'king-addons'),
972 ];
973 if ($this->can_use_pro()) {
974 $column_options['4'] = esc_html__('4 Columns', 'king-addons');
975 $column_options['5'] = esc_html__('5 Columns', 'king-addons');
976 $column_options['6'] = esc_html__('6 Columns', 'king-addons');
977 } else {
978 $column_options['4'] = $this->get_pro_label(esc_html__('4 Columns', 'king-addons'));
979 $column_options['5'] = $this->get_pro_label(esc_html__('5 Columns', 'king-addons'));
980 $column_options['6'] = $this->get_pro_label(esc_html__('6 Columns', 'king-addons'));
981 }
982
983 $this->add_control(
984 'kng_columns',
985 [
986 'label' => esc_html__('Columns', 'king-addons'),
987 'type' => Controls_Manager::SELECT,
988 'default' => '3',
989 'options' => $column_options,
990 'selectors' => [
991 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-columns: {{VALUE}};',
992 ],
993 ]
994 );
995
996 $this->add_responsive_control(
997 'kng_grid_gap',
998 [
999 'label' => esc_html__('Gap', 'king-addons'),
1000 'type' => Controls_Manager::SLIDER,
1001 'size_units' => ['px', 'em'],
1002 'range' => [
1003 'px' => [
1004 'min' => 0,
1005 'max' => 80,
1006 ],
1007 'em' => [
1008 'min' => 0,
1009 'max' => 6,
1010 ],
1011 ],
1012 'default' => [
1013 'size' => 24,
1014 'unit' => 'px',
1015 ],
1016 'selectors' => [
1017 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-gap: {{SIZE}}{{UNIT}};',
1018 ],
1019 ]
1020 );
1021
1022 $this->add_control(
1023 'kng_equal_height',
1024 [
1025 'label' => esc_html__('Equal Height Cards', 'king-addons'),
1026 'type' => Controls_Manager::SWITCHER,
1027 'return_value' => 'yes',
1028 'default' => 'yes',
1029 ]
1030 );
1031
1032 $this->add_control(
1033 'kng_mobile_layout',
1034 [
1035 'label' => esc_html__('Mobile Layout', 'king-addons'),
1036 'type' => Controls_Manager::SELECT,
1037 'default' => 'stack',
1038 'options' => [
1039 'stack' => esc_html__('Stack', 'king-addons'),
1040 'scroll' => esc_html__('Horizontal Scroll', 'king-addons'),
1041 ],
1042 ]
1043 );
1044
1045 $this->add_control(
1046 'kng_hide_scrollbar',
1047 [
1048 'label' => esc_html__('Hide Mobile Scrollbar', 'king-addons'),
1049 'type' => Controls_Manager::SWITCHER,
1050 'return_value' => 'yes',
1051 'default' => '',
1052 'condition' => [
1053 'kng_mobile_layout' => 'scroll',
1054 ],
1055 ]
1056 );
1057
1058 $this->add_responsive_control(
1059 'kng_mobile_card_min_width',
1060 [
1061 'label' => esc_html__('Card Min Width (Mobile)', 'king-addons'),
1062 'type' => Controls_Manager::SLIDER,
1063 'size_units' => ['px'],
1064 'range' => [
1065 'px' => [
1066 'min' => 200,
1067 'max' => 420,
1068 ],
1069 ],
1070 'default' => [
1071 'size' => 260,
1072 'unit' => 'px',
1073 ],
1074 'condition' => [
1075 'kng_mobile_layout' => 'scroll',
1076 ],
1077 'selectors' => [
1078 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-card-min-width: {{SIZE}}{{UNIT}};',
1079 ],
1080 ]
1081 );
1082
1083 $this->end_controls_section();
1084 }
1085
1086 /**
1087 * Register highlight differences controls (Pro).
1088 */
1089 protected function register_highlight_controls(): void
1090 {
1091 $this->start_controls_section(
1092 'kng_highlight_section',
1093 [
1094 'label' => $this->get_pro_label(esc_html__('Highlight Differences', 'king-addons')),
1095 'tab' => Controls_Manager::TAB_CONTENT,
1096 'classes' => $this->get_pro_control_class(),
1097 ]
1098 );
1099
1100 $this->add_control(
1101 'kng_highlight_enable',
1102 [
1103 'label' => $this->get_pro_label(esc_html__('Enable Highlight', 'king-addons')),
1104 'type' => Controls_Manager::SWITCHER,
1105 'return_value' => 'yes',
1106 'default' => '',
1107 'classes' => $this->get_pro_control_class(),
1108 ]
1109 );
1110
1111 $this->add_control(
1112 'kng_highlight_mode',
1113 [
1114 'label' => $this->get_pro_label(esc_html__('Mode', 'king-addons')),
1115 'type' => Controls_Manager::SELECT,
1116 'default' => 'dim',
1117 'options' => [
1118 'dim' => esc_html__('Dim Identical', 'king-addons'),
1119 'hide' => esc_html__('Hide Identical', 'king-addons'),
1120 'unique' => esc_html__('Highlight Unique', 'king-addons'),
1121 ],
1122 'classes' => $this->get_pro_control_class(),
1123 'condition' => [
1124 'kng_highlight_enable' => 'yes',
1125 ],
1126 ]
1127 );
1128
1129 $this->add_control(
1130 'kng_highlight_style',
1131 [
1132 'label' => $this->get_pro_label(esc_html__('Visual Style', 'king-addons')),
1133 'type' => Controls_Manager::SELECT,
1134 'default' => 'background',
1135 'options' => [
1136 'background' => esc_html__('Background', 'king-addons'),
1137 'border' => esc_html__('Border', 'king-addons'),
1138 'icon' => esc_html__('Icon Emphasis', 'king-addons'),
1139 ],
1140 'classes' => $this->get_pro_control_class(),
1141 'condition' => [
1142 'kng_highlight_enable' => 'yes',
1143 'kng_highlight_mode' => 'unique',
1144 ],
1145 ]
1146 );
1147
1148 $this->add_control(
1149 'kng_highlight_compare',
1150 [
1151 'label' => $this->get_pro_label(esc_html__('Compare Mode', 'king-addons')),
1152 'type' => Controls_Manager::SELECT,
1153 'default' => 'normalized',
1154 'options' => [
1155 'strict' => esc_html__('Strict Match', 'king-addons'),
1156 'normalized' => esc_html__('Normalized', 'king-addons'),
1157 ],
1158 'classes' => $this->get_pro_control_class(),
1159 'condition' => [
1160 'kng_highlight_enable' => 'yes',
1161 ],
1162 ]
1163 );
1164
1165 $this->add_control(
1166 'kng_highlight_toggle',
1167 [
1168 'label' => $this->get_pro_label(esc_html__('Show Toggle', 'king-addons')),
1169 'type' => Controls_Manager::SWITCHER,
1170 'return_value' => 'yes',
1171 'default' => '',
1172 'classes' => $this->get_pro_control_class(),
1173 'condition' => [
1174 'kng_highlight_enable' => 'yes',
1175 ],
1176 ]
1177 );
1178
1179 $this->add_control(
1180 'kng_highlight_toggle_label',
1181 [
1182 'label' => $this->get_pro_label(esc_html__('Toggle Label', 'king-addons')),
1183 'type' => Controls_Manager::TEXT,
1184 'default' => esc_html__('Highlight differences', 'king-addons'),
1185 'classes' => $this->get_pro_control_class(),
1186 'condition' => [
1187 'kng_highlight_enable' => 'yes',
1188 'kng_highlight_toggle' => 'yes',
1189 ],
1190 ]
1191 );
1192
1193 $this->add_control(
1194 'kng_highlight_default',
1195 [
1196 'label' => $this->get_pro_label(esc_html__('Default State', 'king-addons')),
1197 'type' => Controls_Manager::SWITCHER,
1198 'return_value' => 'yes',
1199 'default' => 'yes',
1200 'classes' => $this->get_pro_control_class(),
1201 'condition' => [
1202 'kng_highlight_enable' => 'yes',
1203 ],
1204 ]
1205 );
1206
1207 $this->end_controls_section();
1208 }
1209
1210 /**
1211 * Register sticky header controls (Pro).
1212 */
1213 protected function register_sticky_controls(): void
1214 {
1215 $this->start_controls_section(
1216 'kng_sticky_section',
1217 [
1218 'label' => $this->get_pro_label(esc_html__('Sticky Header', 'king-addons')),
1219 'tab' => Controls_Manager::TAB_CONTENT,
1220 'classes' => $this->get_pro_control_class(),
1221 ]
1222 );
1223
1224 $this->add_control(
1225 'kng_sticky_enable',
1226 [
1227 'label' => $this->get_pro_label(esc_html__('Enable Sticky Header', 'king-addons')),
1228 'type' => Controls_Manager::SWITCHER,
1229 'return_value' => 'yes',
1230 'default' => '',
1231 'classes' => $this->get_pro_control_class(),
1232 ]
1233 );
1234
1235 $this->add_control(
1236 'kng_sticky_offset',
1237 [
1238 'label' => $this->get_pro_label(esc_html__('Top Offset', 'king-addons')),
1239 'type' => Controls_Manager::SLIDER,
1240 'size_units' => ['px'],
1241 'range' => [
1242 'px' => [
1243 'min' => 0,
1244 'max' => 120,
1245 ],
1246 ],
1247 'default' => [
1248 'size' => 0,
1249 'unit' => 'px',
1250 ],
1251 'selectors' => [
1252 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-sticky-offset: {{SIZE}}{{UNIT}};',
1253 ],
1254 'classes' => $this->get_pro_control_class(),
1255 'condition' => [
1256 'kng_sticky_enable' => 'yes',
1257 ],
1258 ]
1259 );
1260
1261 $this->add_control(
1262 'kng_sticky_shadow',
1263 [
1264 'label' => $this->get_pro_label(esc_html__('Shadow on Sticky', 'king-addons')),
1265 'type' => Controls_Manager::SWITCHER,
1266 'return_value' => 'yes',
1267 'default' => 'yes',
1268 'classes' => $this->get_pro_control_class(),
1269 'condition' => [
1270 'kng_sticky_enable' => 'yes',
1271 ],
1272 ]
1273 );
1274
1275 $this->add_control(
1276 'kng_sticky_mobile_mode',
1277 [
1278 'label' => $this->get_pro_label(esc_html__('Mobile Mode', 'king-addons')),
1279 'type' => Controls_Manager::SELECT,
1280 'default' => 'compact',
1281 'options' => [
1282 'off' => esc_html__('Off', 'king-addons'),
1283 'compact' => esc_html__('Compact', 'king-addons'),
1284 'full' => esc_html__('Full', 'king-addons'),
1285 ],
1286 'classes' => $this->get_pro_control_class(),
1287 'condition' => [
1288 'kng_sticky_enable' => 'yes',
1289 ],
1290 ]
1291 );
1292
1293 $this->end_controls_section();
1294 }
1295
1296 /**
1297 * Register search/filter controls (Pro).
1298 */
1299 protected function register_search_controls(): void
1300 {
1301 $this->start_controls_section(
1302 'kng_search_section',
1303 [
1304 'label' => $this->get_pro_label(esc_html__('Feature Search', 'king-addons')),
1305 'tab' => Controls_Manager::TAB_CONTENT,
1306 'classes' => $this->get_pro_control_class(),
1307 ]
1308 );
1309
1310 $this->add_control(
1311 'kng_search_enable',
1312 [
1313 'label' => $this->get_pro_label(esc_html__('Enable Search', 'king-addons')),
1314 'type' => Controls_Manager::SWITCHER,
1315 'return_value' => 'yes',
1316 'default' => '',
1317 'classes' => $this->get_pro_control_class(),
1318 ]
1319 );
1320
1321 $this->add_control(
1322 'kng_search_placeholder',
1323 [
1324 'label' => $this->get_pro_label(esc_html__('Placeholder', 'king-addons')),
1325 'type' => Controls_Manager::TEXT,
1326 'default' => esc_html__('Search features', 'king-addons'),
1327 'classes' => $this->get_pro_control_class(),
1328 'condition' => [
1329 'kng_search_enable' => 'yes',
1330 ],
1331 ]
1332 );
1333
1334 $this->add_control(
1335 'kng_search_includes_tooltip',
1336 [
1337 'label' => $this->get_pro_label(esc_html__('Search Tooltips', 'king-addons')),
1338 'type' => Controls_Manager::SWITCHER,
1339 'return_value' => 'yes',
1340 'default' => 'yes',
1341 'classes' => $this->get_pro_control_class(),
1342 'condition' => [
1343 'kng_search_enable' => 'yes',
1344 ],
1345 ]
1346 );
1347
1348 $this->end_controls_section();
1349 }
1350
1351 /**
1352 * Register card style controls.
1353 */
1354 protected function register_style_card_controls(): void
1355 {
1356 $this->start_controls_section(
1357 'kng_style_card_section',
1358 [
1359 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Card', 'king-addons'),
1360 'tab' => Controls_Manager::TAB_STYLE,
1361 ]
1362 );
1363
1364 $this->add_group_control(
1365 Group_Control_Background::get_type(),
1366 [
1367 'name' => 'kng_card_background',
1368 'types' => ['classic', 'gradient'],
1369 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__card',
1370 ]
1371 );
1372
1373 $this->add_group_control(
1374 Group_Control_Border::get_type(),
1375 [
1376 'name' => 'kng_card_border',
1377 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__card',
1378 ]
1379 );
1380
1381 $this->add_control(
1382 'kng_card_radius',
1383 [
1384 'label' => esc_html__('Border Radius', 'king-addons'),
1385 'type' => Controls_Manager::SLIDER,
1386 'size_units' => ['px', '%'],
1387 'range' => [
1388 'px' => ['min' => 0, 'max' => 60],
1389 '%' => ['min' => 0, 'max' => 100],
1390 ],
1391 'selectors' => [
1392 '{{WRAPPER}} .king-addons-comparison-matrix-cards__card' => 'border-radius: {{SIZE}}{{UNIT}};',
1393 ],
1394 ]
1395 );
1396
1397 $this->add_group_control(
1398 Group_Control_Box_Shadow::get_type(),
1399 [
1400 'name' => 'kng_card_shadow',
1401 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__card',
1402 ]
1403 );
1404
1405 $this->add_responsive_control(
1406 'kng_card_padding',
1407 [
1408 'label' => esc_html__('Padding', 'king-addons'),
1409 'type' => Controls_Manager::DIMENSIONS,
1410 'size_units' => ['px', 'em', '%'],
1411 'selectors' => [
1412 '{{WRAPPER}} .king-addons-comparison-matrix-cards__card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1413 ],
1414 ]
1415 );
1416
1417 $this->add_control(
1418 'kng_card_hover_heading',
1419 [
1420 'label' => esc_html__('Hover', 'king-addons'),
1421 'type' => Controls_Manager::HEADING,
1422 'separator' => 'before',
1423 ]
1424 );
1425
1426 $this->add_control(
1427 'kng_card_hover_background',
1428 [
1429 'label' => esc_html__('Hover Background', 'king-addons'),
1430 'type' => Controls_Manager::COLOR,
1431 'selectors' => [
1432 '{{WRAPPER}} .king-addons-comparison-matrix-cards__card:hover' => 'background: {{VALUE}};',
1433 ],
1434 ]
1435 );
1436
1437 $this->add_control(
1438 'kng_card_hover_border',
1439 [
1440 'label' => esc_html__('Hover Border Color', 'king-addons'),
1441 'type' => Controls_Manager::COLOR,
1442 'selectors' => [
1443 '{{WRAPPER}} .king-addons-comparison-matrix-cards__card:hover' => 'border-color: {{VALUE}};',
1444 ],
1445 ]
1446 );
1447
1448 $this->add_group_control(
1449 Group_Control_Box_Shadow::get_type(),
1450 [
1451 'name' => 'kng_card_hover_shadow',
1452 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__card:hover',
1453 ]
1454 );
1455
1456 $this->add_control(
1457 'kng_card_hover_translate',
1458 [
1459 'label' => esc_html__('Hover Lift', 'king-addons'),
1460 'type' => Controls_Manager::SLIDER,
1461 'size_units' => ['px'],
1462 'range' => [
1463 'px' => [
1464 'min' => -30,
1465 'max' => 30,
1466 ],
1467 ],
1468 'selectors' => [
1469 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-card-hover-translate: {{SIZE}}{{UNIT}};',
1470 ],
1471 ]
1472 );
1473
1474 $this->end_controls_section();
1475 }
1476
1477 /**
1478 * Register header style controls.
1479 */
1480 protected function register_style_header_controls(): void
1481 {
1482 $this->start_controls_section(
1483 'kng_style_header_section',
1484 [
1485 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Header', 'king-addons'),
1486 'tab' => Controls_Manager::TAB_STYLE,
1487 ]
1488 );
1489
1490 $this->add_control(
1491 'kng_title_color',
1492 [
1493 'label' => esc_html__('Title Color', 'king-addons'),
1494 'type' => Controls_Manager::COLOR,
1495 'selectors' => [
1496 '{{WRAPPER}} .king-addons-comparison-matrix-cards__title' => 'color: {{VALUE}};',
1497 ],
1498 ]
1499 );
1500
1501 $this->add_group_control(
1502 Group_Control_Typography::get_type(),
1503 [
1504 'name' => 'kng_title_typography',
1505 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__title',
1506 ]
1507 );
1508
1509 $this->add_control(
1510 'kng_price_color',
1511 [
1512 'label' => esc_html__('Price Color', 'king-addons'),
1513 'type' => Controls_Manager::COLOR,
1514 'selectors' => [
1515 '{{WRAPPER}} .king-addons-comparison-matrix-cards__price' => 'color: {{VALUE}};',
1516 ],
1517 ]
1518 );
1519
1520 $this->add_group_control(
1521 Group_Control_Typography::get_type(),
1522 [
1523 'name' => 'kng_price_typography',
1524 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__price',
1525 ]
1526 );
1527
1528 $this->add_control(
1529 'kng_subtitle_color',
1530 [
1531 'label' => esc_html__('Subtitle Color', 'king-addons'),
1532 'type' => Controls_Manager::COLOR,
1533 'selectors' => [
1534 '{{WRAPPER}} .king-addons-comparison-matrix-cards__subtitle' => 'color: {{VALUE}};',
1535 '{{WRAPPER}} .king-addons-comparison-matrix-cards__description' => 'color: {{VALUE}};',
1536 ],
1537 ]
1538 );
1539
1540 $this->add_group_control(
1541 Group_Control_Typography::get_type(),
1542 [
1543 'name' => 'kng_subtitle_typography',
1544 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__subtitle, {{WRAPPER}} .king-addons-comparison-matrix-cards__description',
1545 ]
1546 );
1547
1548 $this->add_control(
1549 'kng_header_gap',
1550 [
1551 'label' => esc_html__('Header Spacing', 'king-addons'),
1552 'type' => Controls_Manager::SLIDER,
1553 'size_units' => ['px', 'em'],
1554 'range' => [
1555 'px' => [
1556 'min' => 0,
1557 'max' => 40,
1558 ],
1559 'em' => [
1560 'min' => 0,
1561 'max' => 3,
1562 ],
1563 ],
1564 'selectors' => [
1565 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-header-gap: {{SIZE}}{{UNIT}};',
1566 ],
1567 ]
1568 );
1569
1570 $this->add_control(
1571 'kng_badge_color',
1572 [
1573 'label' => esc_html__('Badge Text Color', 'king-addons'),
1574 'type' => Controls_Manager::COLOR,
1575 'selectors' => [
1576 '{{WRAPPER}} .king-addons-comparison-matrix-cards__badge' => 'color: {{VALUE}};',
1577 ],
1578 ]
1579 );
1580
1581 $this->add_control(
1582 'kng_badge_background',
1583 [
1584 'label' => esc_html__('Badge Background', 'king-addons'),
1585 'type' => Controls_Manager::COLOR,
1586 'selectors' => [
1587 '{{WRAPPER}} .king-addons-comparison-matrix-cards__badge' => 'background-color: {{VALUE}};',
1588 ],
1589 ]
1590 );
1591
1592 $this->end_controls_section();
1593 }
1594
1595 /**
1596 * Register feature style controls.
1597 */
1598 protected function register_style_feature_controls(): void
1599 {
1600 $this->start_controls_section(
1601 'kng_style_features_section',
1602 [
1603 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Features', 'king-addons'),
1604 'tab' => Controls_Manager::TAB_STYLE,
1605 ]
1606 );
1607
1608 $this->add_control(
1609 'kng_feature_label_color',
1610 [
1611 'label' => esc_html__('Label Color', 'king-addons'),
1612 'type' => Controls_Manager::COLOR,
1613 'selectors' => [
1614 '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature-label' => 'color: {{VALUE}};',
1615 ],
1616 ]
1617 );
1618
1619 $this->add_group_control(
1620 Group_Control_Typography::get_type(),
1621 [
1622 'name' => 'kng_feature_label_typography',
1623 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature-label',
1624 ]
1625 );
1626
1627 $this->add_control(
1628 'kng_feature_value_color',
1629 [
1630 'label' => esc_html__('Value Color', 'king-addons'),
1631 'type' => Controls_Manager::COLOR,
1632 'selectors' => [
1633 '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature-value' => 'color: {{VALUE}};',
1634 ],
1635 ]
1636 );
1637
1638 $this->add_group_control(
1639 Group_Control_Typography::get_type(),
1640 [
1641 'name' => 'kng_feature_value_typography',
1642 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature-value',
1643 ]
1644 );
1645
1646 $this->add_control(
1647 'kng_feature_icon_size',
1648 [
1649 'label' => esc_html__('Icon Size', 'king-addons'),
1650 'type' => Controls_Manager::SLIDER,
1651 'size_units' => ['px'],
1652 'range' => [
1653 'px' => [
1654 'min' => 12,
1655 'max' => 32,
1656 ],
1657 ],
1658 'selectors' => [
1659 '{{WRAPPER}} .king-addons-comparison-matrix-cards__icon' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
1660 ],
1661 ]
1662 );
1663
1664 $this->add_control(
1665 'kng_feature_icon_included',
1666 [
1667 'label' => esc_html__('Included Icon Color', 'king-addons'),
1668 'type' => Controls_Manager::COLOR,
1669 'selectors' => [
1670 '{{WRAPPER}} .king-addons-comparison-matrix-cards__icon.is-included' => 'color: {{VALUE}};',
1671 ],
1672 ]
1673 );
1674
1675 $this->add_control(
1676 'kng_feature_icon_excluded',
1677 [
1678 'label' => esc_html__('Excluded Icon Color', 'king-addons'),
1679 'type' => Controls_Manager::COLOR,
1680 'selectors' => [
1681 '{{WRAPPER}} .king-addons-comparison-matrix-cards__icon.is-excluded' => 'color: {{VALUE}};',
1682 ],
1683 ]
1684 );
1685
1686 $this->add_control(
1687 'kng_feature_icon_limited',
1688 [
1689 'label' => esc_html__('Limited Icon Color', 'king-addons'),
1690 'type' => Controls_Manager::COLOR,
1691 'selectors' => [
1692 '{{WRAPPER}} .king-addons-comparison-matrix-cards__icon.is-limited' => 'color: {{VALUE}};',
1693 ],
1694 ]
1695 );
1696
1697 $this->add_control(
1698 'kng_feature_spacing',
1699 [
1700 'label' => esc_html__('Item Spacing', 'king-addons'),
1701 'type' => Controls_Manager::SLIDER,
1702 'size_units' => ['px'],
1703 'range' => [
1704 'px' => [
1705 'min' => 4,
1706 'max' => 32,
1707 ],
1708 ],
1709 'selectors' => [
1710 '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature' => 'gap: {{SIZE}}{{UNIT}};',
1711 '{{WRAPPER}} .king-addons-comparison-matrix-cards__feature:not(:last-child)' => 'padding-bottom: {{SIZE}}{{UNIT}};',
1712 ],
1713 ]
1714 );
1715
1716 $this->add_control(
1717 'kng_feature_divider_color',
1718 [
1719 'label' => esc_html__('Divider Color', 'king-addons'),
1720 'type' => Controls_Manager::COLOR,
1721 'selectors' => [
1722 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-divider-color: {{VALUE}};',
1723 ],
1724 ]
1725 );
1726
1727 $this->add_control(
1728 'kng_feature_divider_style',
1729 [
1730 'label' => esc_html__('Divider Style', 'king-addons'),
1731 'type' => Controls_Manager::SELECT,
1732 'default' => 'dashed',
1733 'options' => [
1734 'solid' => esc_html__('Solid', 'king-addons'),
1735 'dashed' => esc_html__('Dashed', 'king-addons'),
1736 'dotted' => esc_html__('Dotted', 'king-addons'),
1737 ],
1738 'selectors' => [
1739 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-divider-style: {{VALUE}};',
1740 ],
1741 ]
1742 );
1743
1744 $this->add_control(
1745 'kng_feature_divider_width',
1746 [
1747 'label' => esc_html__('Divider Width', 'king-addons'),
1748 'type' => Controls_Manager::SLIDER,
1749 'size_units' => ['px'],
1750 'range' => [
1751 'px' => [
1752 'min' => 0,
1753 'max' => 4,
1754 ],
1755 ],
1756 'selectors' => [
1757 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-divider-width: {{SIZE}}{{UNIT}};',
1758 ],
1759 ]
1760 );
1761
1762 $this->add_control(
1763 'kng_feature_group_color',
1764 [
1765 'label' => esc_html__('Group Color', 'king-addons'),
1766 'type' => Controls_Manager::COLOR,
1767 'selectors' => [
1768 '{{WRAPPER}} .king-addons-comparison-matrix-cards__group' => 'color: {{VALUE}};',
1769 ],
1770 ]
1771 );
1772
1773 $this->add_group_control(
1774 Group_Control_Typography::get_type(),
1775 [
1776 'name' => 'kng_feature_group_typography',
1777 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__group',
1778 ]
1779 );
1780
1781 $this->add_control(
1782 'kng_feature_group_background',
1783 [
1784 'label' => esc_html__('Group Background', 'king-addons'),
1785 'type' => Controls_Manager::COLOR,
1786 'selectors' => [
1787 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-group-bg: {{VALUE}};',
1788 ],
1789 ]
1790 );
1791
1792 $this->add_responsive_control(
1793 'kng_feature_group_padding',
1794 [
1795 'label' => esc_html__('Group Padding', 'king-addons'),
1796 'type' => Controls_Manager::DIMENSIONS,
1797 'size_units' => ['px', 'em'],
1798 'selectors' => [
1799 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-group-padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1800 ],
1801 ]
1802 );
1803
1804 $this->add_control(
1805 'kng_feature_group_radius',
1806 [
1807 'label' => esc_html__('Group Border Radius', 'king-addons'),
1808 'type' => Controls_Manager::SLIDER,
1809 'size_units' => ['px', '%'],
1810 'range' => [
1811 'px' => ['min' => 0, 'max' => 40],
1812 '%' => ['min' => 0, 'max' => 100],
1813 ],
1814 'selectors' => [
1815 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-group-radius: {{SIZE}}{{UNIT}};',
1816 ],
1817 ]
1818 );
1819
1820 $this->add_control(
1821 'kng_tooltip_background',
1822 [
1823 'label' => esc_html__('Tooltip Background', 'king-addons'),
1824 'type' => Controls_Manager::COLOR,
1825 'selectors' => [
1826 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-tooltip-bg: {{VALUE}};',
1827 ],
1828 ]
1829 );
1830
1831 $this->add_control(
1832 'kng_tooltip_text',
1833 [
1834 'label' => esc_html__('Tooltip Text Color', 'king-addons'),
1835 'type' => Controls_Manager::COLOR,
1836 'selectors' => [
1837 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-tooltip-text: {{VALUE}};',
1838 ],
1839 ]
1840 );
1841
1842 $this->add_group_control(
1843 Group_Control_Typography::get_type(),
1844 [
1845 'name' => 'kng_tooltip_typography',
1846 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__tooltip-text',
1847 ]
1848 );
1849
1850 $this->end_controls_section();
1851 }
1852
1853 /**
1854 * Register button style controls.
1855 */
1856 protected function register_style_button_controls(): void
1857 {
1858 $this->start_controls_section(
1859 'kng_style_button_section',
1860 [
1861 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Button', 'king-addons'),
1862 'tab' => Controls_Manager::TAB_STYLE,
1863 ]
1864 );
1865
1866 $this->add_control(
1867 'kng_button_alignment',
1868 [
1869 'label' => esc_html__('Alignment', 'king-addons'),
1870 'type' => Controls_Manager::SELECT,
1871 'default' => 'flex-start',
1872 'options' => [
1873 'flex-start' => esc_html__('Left', 'king-addons'),
1874 'center' => esc_html__('Center', 'king-addons'),
1875 'flex-end' => esc_html__('Right', 'king-addons'),
1876 ],
1877 'selectors' => [
1878 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button' => 'align-self: {{VALUE}};',
1879 ],
1880 ]
1881 );
1882
1883 $this->add_group_control(
1884 Group_Control_Typography::get_type(),
1885 [
1886 'name' => 'kng_button_typography',
1887 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__button',
1888 ]
1889 );
1890
1891 $this->add_control(
1892 'kng_button_text_color',
1893 [
1894 'label' => esc_html__('Text Color', 'king-addons'),
1895 'type' => Controls_Manager::COLOR,
1896 'selectors' => [
1897 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button' => 'color: {{VALUE}};',
1898 ],
1899 ]
1900 );
1901
1902 $this->add_control(
1903 'kng_button_background',
1904 [
1905 'label' => esc_html__('Background', 'king-addons'),
1906 'type' => Controls_Manager::COLOR,
1907 'selectors' => [
1908 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button' => 'background-color: {{VALUE}};',
1909 ],
1910 ]
1911 );
1912
1913 $this->add_control(
1914 'kng_button_hover_text_color',
1915 [
1916 'label' => esc_html__('Hover Text Color', 'king-addons'),
1917 'type' => Controls_Manager::COLOR,
1918 'selectors' => [
1919 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button:hover' => 'color: {{VALUE}};',
1920 ],
1921 ]
1922 );
1923
1924 $this->add_control(
1925 'kng_button_hover_background',
1926 [
1927 'label' => esc_html__('Hover Background', 'king-addons'),
1928 'type' => Controls_Manager::COLOR,
1929 'selectors' => [
1930 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button:hover' => 'background-color: {{VALUE}};',
1931 ],
1932 ]
1933 );
1934
1935 $this->add_group_control(
1936 Group_Control_Border::get_type(),
1937 [
1938 'name' => 'kng_button_border',
1939 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__button',
1940 ]
1941 );
1942
1943 $this->add_control(
1944 'kng_button_radius',
1945 [
1946 'label' => esc_html__('Border Radius', 'king-addons'),
1947 'type' => Controls_Manager::SLIDER,
1948 'size_units' => ['px', '%'],
1949 'range' => [
1950 'px' => ['min' => 0, 'max' => 60],
1951 '%' => ['min' => 0, 'max' => 100],
1952 ],
1953 'selectors' => [
1954 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button' => 'border-radius: {{SIZE}}{{UNIT}};',
1955 ],
1956 ]
1957 );
1958
1959 $this->add_responsive_control(
1960 'kng_button_padding',
1961 [
1962 'label' => esc_html__('Padding', 'king-addons'),
1963 'type' => Controls_Manager::DIMENSIONS,
1964 'size_units' => ['px', 'em'],
1965 'selectors' => [
1966 '{{WRAPPER}} .king-addons-comparison-matrix-cards__button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1967 ],
1968 ]
1969 );
1970
1971 $this->end_controls_section();
1972 }
1973
1974 /**
1975 * Register highlight style controls (Pro).
1976 */
1977 protected function register_style_highlight_controls(): void
1978 {
1979 $this->start_controls_section(
1980 'kng_style_highlight_section',
1981 [
1982 'label' => $this->get_pro_label(esc_html__('Highlight Styles', 'king-addons')),
1983 'tab' => Controls_Manager::TAB_STYLE,
1984 'classes' => $this->get_pro_control_class(),
1985 ]
1986 );
1987
1988 $this->add_control(
1989 'kng_highlight_dim_opacity',
1990 [
1991 'label' => $this->get_pro_label(esc_html__('Dim Opacity', 'king-addons')),
1992 'type' => Controls_Manager::SLIDER,
1993 'size_units' => ['%'],
1994 'range' => [
1995 '%' => [
1996 'min' => 10,
1997 'max' => 100,
1998 ],
1999 ],
2000 'default' => [
2001 'size' => 45,
2002 'unit' => '%',
2003 ],
2004 'selectors' => [
2005 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-dim-opacity: calc({{SIZE}} / 100);',
2006 ],
2007 'classes' => $this->get_pro_control_class(),
2008 ]
2009 );
2010
2011 $this->add_control(
2012 'kng_highlight_unique_background',
2013 [
2014 'label' => $this->get_pro_label(esc_html__('Unique Background', 'king-addons')),
2015 'type' => Controls_Manager::COLOR,
2016 'selectors' => [
2017 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-unique-bg: {{VALUE}};',
2018 ],
2019 'classes' => $this->get_pro_control_class(),
2020 ]
2021 );
2022
2023 $this->add_control(
2024 'kng_highlight_unique_border',
2025 [
2026 'label' => $this->get_pro_label(esc_html__('Unique Border', 'king-addons')),
2027 'type' => Controls_Manager::COLOR,
2028 'selectors' => [
2029 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-unique-border: {{VALUE}};',
2030 ],
2031 'classes' => $this->get_pro_control_class(),
2032 ]
2033 );
2034
2035 $this->add_control(
2036 'kng_highlight_unique_text',
2037 [
2038 'label' => $this->get_pro_label(esc_html__('Unique Text', 'king-addons')),
2039 'type' => Controls_Manager::COLOR,
2040 'selectors' => [
2041 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-unique-text: {{VALUE}};',
2042 ],
2043 'classes' => $this->get_pro_control_class(),
2044 ]
2045 );
2046
2047 $this->end_controls_section();
2048 }
2049
2050 /**
2051 * Register sticky style controls (Pro).
2052 */
2053 protected function register_style_sticky_controls(): void
2054 {
2055 $this->start_controls_section(
2056 'kng_style_sticky_section',
2057 [
2058 'label' => $this->get_pro_label(esc_html__('Sticky Styles', 'king-addons')),
2059 'tab' => Controls_Manager::TAB_STYLE,
2060 'classes' => $this->get_pro_control_class(),
2061 ]
2062 );
2063
2064 $this->add_control(
2065 'kng_sticky_background',
2066 [
2067 'label' => $this->get_pro_label(esc_html__('Sticky Background', 'king-addons')),
2068 'type' => Controls_Manager::COLOR,
2069 'selectors' => [
2070 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-sticky-bg: {{VALUE}};',
2071 ],
2072 'classes' => $this->get_pro_control_class(),
2073 ]
2074 );
2075
2076 $this->add_control(
2077 'kng_sticky_divider',
2078 [
2079 'label' => $this->get_pro_label(esc_html__('Sticky Divider', 'king-addons')),
2080 'type' => Controls_Manager::COLOR,
2081 'selectors' => [
2082 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-sticky-divider: {{VALUE}};',
2083 ],
2084 'classes' => $this->get_pro_control_class(),
2085 ]
2086 );
2087
2088 $this->add_control(
2089 'kng_sticky_shadow_color',
2090 [
2091 'label' => $this->get_pro_label(esc_html__('Sticky Shadow', 'king-addons')),
2092 'type' => Controls_Manager::COLOR,
2093 'selectors' => [
2094 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-sticky-shadow: {{VALUE}};',
2095 ],
2096 'classes' => $this->get_pro_control_class(),
2097 ]
2098 );
2099
2100 $this->end_controls_section();
2101 }
2102
2103 /**
2104 * Register search style controls (Pro).
2105 */
2106 protected function register_style_search_controls(): void
2107 {
2108 $this->start_controls_section(
2109 'kng_style_search_section',
2110 [
2111 'label' => $this->get_pro_label(esc_html__('Search Styles', 'king-addons')),
2112 'tab' => Controls_Manager::TAB_STYLE,
2113 'classes' => $this->get_pro_control_class(),
2114 ]
2115 );
2116
2117 $this->add_control(
2118 'kng_search_background',
2119 [
2120 'label' => $this->get_pro_label(esc_html__('Background', 'king-addons')),
2121 'type' => Controls_Manager::COLOR,
2122 'selectors' => [
2123 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-search-bg: {{VALUE}};',
2124 ],
2125 'classes' => $this->get_pro_control_class(),
2126 ]
2127 );
2128
2129 $this->add_control(
2130 'kng_search_border',
2131 [
2132 'label' => $this->get_pro_label(esc_html__('Border Color', 'king-addons')),
2133 'type' => Controls_Manager::COLOR,
2134 'selectors' => [
2135 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-search-border: {{VALUE}};',
2136 ],
2137 'classes' => $this->get_pro_control_class(),
2138 ]
2139 );
2140
2141 $this->add_control(
2142 'kng_search_text_color',
2143 [
2144 'label' => $this->get_pro_label(esc_html__('Text Color', 'king-addons')),
2145 'type' => Controls_Manager::COLOR,
2146 'selectors' => [
2147 '{{WRAPPER}} .king-addons-comparison-matrix-cards' => '--kng-cmc-search-text: {{VALUE}};',
2148 ],
2149 'classes' => $this->get_pro_control_class(),
2150 ]
2151 );
2152
2153 $this->add_group_control(
2154 Group_Control_Typography::get_type(),
2155 [
2156 'name' => 'kng_search_typography',
2157 'selector' => '{{WRAPPER}} .king-addons-comparison-matrix-cards__search-input',
2158 'classes' => $this->get_pro_control_class(),
2159 ]
2160 );
2161
2162 $this->add_control(
2163 'kng_search_radius',
2164 [
2165 'label' => $this->get_pro_label(esc_html__('Border Radius', 'king-addons')),
2166 'type' => Controls_Manager::SLIDER,
2167 'size_units' => ['px', '%'],
2168 'range' => [
2169 'px' => ['min' => 0, 'max' => 40],
2170 '%' => ['min' => 0, 'max' => 100],
2171 ],
2172 'selectors' => [
2173 '{{WRAPPER}} .king-addons-comparison-matrix-cards__search-input' => 'border-radius: {{SIZE}}{{UNIT}};',
2174 ],
2175 'classes' => $this->get_pro_control_class(),
2176 ]
2177 );
2178
2179 $this->add_responsive_control(
2180 'kng_search_padding',
2181 [
2182 'label' => $this->get_pro_label(esc_html__('Padding', 'king-addons')),
2183 'type' => Controls_Manager::DIMENSIONS,
2184 'size_units' => ['px', 'em'],
2185 'selectors' => [
2186 '{{WRAPPER}} .king-addons-comparison-matrix-cards__search-input' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
2187 ],
2188 'classes' => $this->get_pro_control_class(),
2189 ]
2190 );
2191
2192 $this->end_controls_section();
2193 }
2194
2195 /**
2196 * Render premium upgrade notice.
2197 */
2198 public function register_pro_notice_controls(): void
2199 {
2200 if (!$this->can_use_pro()) {
2201 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'comparison-matrix-cards', [
2202 'Highlight differences modes and visitor toggle',
2203 'Sticky header inside widget',
2204 'Dynamic data sources (ACF, WooCommerce)',
2205 'Feature search and filtering',
2206 ]);
2207 }
2208 }
2209
2210 /**
2211 * Add per-plan feature value controls.
2212 *
2213 * @param Repeater $repeater Repeater instance.
2214 * @param int $index Plan index.
2215 * @param string $label Label.
2216 * @param array $condition Optional condition.
2217 * @param string $classes Optional classes.
2218 */
2219 protected function add_feature_value_controls(Repeater $repeater, int $index, string $label, array $condition = [], string $classes = ''): void
2220 {
2221 $type_key = 'kng_feature_value_' . $index . '_type';
2222 $text_key = 'kng_feature_value_' . $index . '_text';
2223
2224 $type_control = [
2225 'label' => $label . ' ' . esc_html__('Value Type', 'king-addons'),
2226 'type' => Controls_Manager::SELECT,
2227 'default' => 'included',
2228 'options' => [
2229 'included' => esc_html__('Included', 'king-addons'),
2230 'excluded' => esc_html__('Excluded', 'king-addons'),
2231 'limited' => esc_html__('Limited', 'king-addons'),
2232 'text' => esc_html__('Text', 'king-addons'),
2233 ],
2234 'separator' => 'before',
2235 ];
2236
2237 if (!empty($condition)) {
2238 $type_control['condition'] = $condition;
2239 }
2240
2241 if ($classes !== '') {
2242 $type_control['classes'] = $classes;
2243 }
2244
2245 $repeater->add_control($type_key, $type_control);
2246
2247 $text_condition = [
2248 $type_key => 'text',
2249 ];
2250
2251 if (!empty($condition)) {
2252 $text_condition = array_merge($text_condition, $condition);
2253 }
2254
2255 $text_control = [
2256 'label' => $label . ' ' . esc_html__('Text', 'king-addons'),
2257 'type' => Controls_Manager::TEXT,
2258 'default' => '',
2259 'condition' => $text_condition,
2260 ];
2261
2262 if ($classes !== '') {
2263 $text_control['classes'] = $classes;
2264 }
2265
2266 $repeater->add_control($text_key, $text_control);
2267 }
2268
2269 /**
2270 * Build render context.
2271 *
2272 * @param array<string, mixed> $settings Settings.
2273 * @param int $plan_count Plan count.
2274 *
2275 * @return array<string, mixed>
2276 */
2277 protected function get_render_context(array $settings, int $plan_count): array
2278 {
2279 $can_pro = $this->can_use_pro();
2280
2281 $highlight_enabled = $can_pro && (($settings['kng_highlight_enable'] ?? '') === 'yes');
2282 $search_enabled = $can_pro && (($settings['kng_search_enable'] ?? '') === 'yes');
2283 $sticky_enabled = $can_pro && (($settings['kng_sticky_enable'] ?? '') === 'yes');
2284
2285 return [
2286 'plan_count' => $plan_count,
2287 'preset' => $settings['kng_preset'] ?? 'minimal',
2288 'mobile_layout' => $settings['kng_mobile_layout'] ?? 'stack',
2289 'equal_height' => ($settings['kng_equal_height'] ?? '') === 'yes',
2290 'hide_scrollbar' => ($settings['kng_hide_scrollbar'] ?? '') === 'yes',
2291 'highlight_enabled' => $highlight_enabled,
2292 'highlight_mode' => $settings['kng_highlight_mode'] ?? 'dim',
2293 'highlight_compare' => $settings['kng_highlight_compare'] ?? 'normalized',
2294 'highlight_style' => $settings['kng_highlight_style'] ?? 'background',
2295 'highlight_default' => ($settings['kng_highlight_default'] ?? '') === 'yes',
2296 'highlight_toggle' => $highlight_enabled && (($settings['kng_highlight_toggle'] ?? '') === 'yes'),
2297 'highlight_label' => $settings['kng_highlight_toggle_label'] ?? esc_html__('Highlight differences', 'king-addons'),
2298 'search_enabled' => $search_enabled,
2299 'search_placeholder' => $settings['kng_search_placeholder'] ?? esc_html__('Search features', 'king-addons'),
2300 'search_tooltip' => ($settings['kng_search_includes_tooltip'] ?? '') === 'yes',
2301 'sticky_enabled' => $sticky_enabled,
2302 'sticky_shadow' => ($settings['kng_sticky_shadow'] ?? '') === 'yes',
2303 'sticky_mobile_mode' => $settings['kng_sticky_mobile_mode'] ?? 'compact',
2304 ];
2305 }
2306
2307 /**
2308 * Build wrapper classes.
2309 *
2310 * @param array<string, mixed> $settings Settings.
2311 * @param array<string, mixed> $context Context.
2312 *
2313 * @return array<int, string>
2314 */
2315 protected function get_wrapper_classes(array $settings, array $context): array
2316 {
2317 $classes = [
2318 'king-addons-comparison-matrix-cards',
2319 'king-addons-comparison-matrix-cards--preset-' . ($context['preset'] ?? 'minimal'),
2320 'king-addons-comparison-matrix-cards--mobile-' . ($context['mobile_layout'] ?? 'stack'),
2321 ];
2322
2323 if (!empty($context['equal_height'])) {
2324 $classes[] = 'king-addons-comparison-matrix-cards--equal-height';
2325 }
2326
2327 if (!empty($context['hide_scrollbar'])) {
2328 $classes[] = 'king-addons-comparison-matrix-cards--hide-scrollbar';
2329 }
2330
2331 if (!empty($context['highlight_enabled'])) {
2332 $classes[] = 'is-highlight-enabled';
2333 $classes[] = 'is-highlight-mode-' . ($context['highlight_mode'] ?? 'dim');
2334 $classes[] = 'is-highlight-style-' . ($context['highlight_style'] ?? 'background');
2335 }
2336
2337 if (!empty($context['search_enabled'])) {
2338 $classes[] = 'has-search';
2339 }
2340
2341 if (!empty($context['sticky_enabled'])) {
2342 $classes[] = 'is-sticky-enabled';
2343 if (!empty($context['sticky_shadow'])) {
2344 $classes[] = 'has-sticky-shadow';
2345 }
2346 $mobile_mode = $context['sticky_mobile_mode'] ?? 'compact';
2347 if ($mobile_mode === 'off') {
2348 $classes[] = 'is-sticky-mobile-off';
2349 } elseif ($mobile_mode === 'compact') {
2350 $classes[] = 'is-sticky-compact';
2351 }
2352 }
2353
2354 unset($settings);
2355 return $classes;
2356 }
2357
2358 /**
2359 * Build wrapper attributes.
2360 *
2361 * @param array<string, mixed> $settings Settings.
2362 * @param array<string, mixed> $context Context.
2363 *
2364 * @return string
2365 */
2366 protected function get_wrapper_attributes(array $settings, array $context): string
2367 {
2368 $columns_setting = isset($settings['kng_columns']) ? (int) $settings['kng_columns'] : 3;
2369 $columns_setting = max(1, min($columns_setting, $this->can_use_pro() ? 6 : 3));
2370 $plan_count = (int) ($context['plan_count'] ?? 0);
2371 $columns = $plan_count > 0 ? min($columns_setting, $plan_count) : $columns_setting;
2372
2373 $attributes = [
2374 'data-widget-id' => $this->get_id(),
2375 'data-plan-count' => (string) ($context['plan_count'] ?? 0),
2376 'data-mobile-layout' => (string) ($context['mobile_layout'] ?? 'stack'),
2377 'data-highlight-enable' => !empty($context['highlight_enabled']) ? 'yes' : 'no',
2378 'data-highlight-mode' => (string) ($context['highlight_mode'] ?? 'dim'),
2379 'data-highlight-compare' => (string) ($context['highlight_compare'] ?? 'normalized'),
2380 'data-highlight-style' => (string) ($context['highlight_style'] ?? 'background'),
2381 'data-highlight-default' => !empty($context['highlight_default']) ? 'yes' : 'no',
2382 'data-highlight-toggle' => !empty($context['highlight_toggle']) ? 'yes' : 'no',
2383 'data-search-enable' => !empty($context['search_enabled']) ? 'yes' : 'no',
2384 'data-search-tooltip' => !empty($context['search_tooltip']) ? 'yes' : 'no',
2385 'data-sticky-enable' => !empty($context['sticky_enabled']) ? 'yes' : 'no',
2386 'data-sticky-shadow' => !empty($context['sticky_shadow']) ? 'yes' : 'no',
2387 'style' => '--kng-cmc-columns: ' . $columns . ';',
2388 ];
2389
2390 $output = [];
2391 foreach ($attributes as $key => $value) {
2392 $output[] = esc_attr($key) . '="' . esc_attr($value) . '"';
2393 }
2394
2395 if (!empty($settings['kng_sticky_offset']['size'])) {
2396 $output[] = 'data-sticky-offset="' . esc_attr((string) $settings['kng_sticky_offset']['size']) . '"';
2397 }
2398
2399 return $output ? ' ' . implode(' ', $output) : '';
2400 }
2401
2402 /**
2403 * Render controls bar (search + toggle).
2404 *
2405 * @param array<string, mixed> $context Context.
2406 */
2407 protected function render_controls_bar(array $context): void
2408 {
2409 $has_search = !empty($context['search_enabled']);
2410 $has_toggle = !empty($context['highlight_toggle']);
2411
2412 if (!$has_search && !$has_toggle) {
2413 return;
2414 }
2415
2416 ?>
2417 <div class="king-addons-comparison-matrix-cards__controls">
2418 <?php if ($has_search) : ?>
2419 <label class="king-addons-comparison-matrix-cards__search">
2420 <span class="screen-reader-text"><?php echo esc_html__('Search features', 'king-addons'); ?></span>
2421 <input
2422 class="king-addons-comparison-matrix-cards__search-input"
2423 type="search"
2424 placeholder="<?php echo esc_attr($context['search_placeholder'] ?? esc_html__('Search features', 'king-addons')); ?>"
2425 />
2426 </label>
2427 <?php endif; ?>
2428 <?php if ($has_toggle) : ?>
2429 <label class="king-addons-comparison-matrix-cards__highlight-toggle">
2430 <input class="king-addons-comparison-matrix-cards__highlight-input" type="checkbox" />
2431 <span class="king-addons-comparison-matrix-cards__toggle-indicator" aria-hidden="true"></span>
2432 <span class="king-addons-comparison-matrix-cards__toggle-label">
2433 <?php echo esc_html($context['highlight_label'] ?? esc_html__('Highlight differences', 'king-addons')); ?>
2434 </span>
2435 </label>
2436 <?php endif; ?>
2437 </div>
2438 <?php
2439 }
2440
2441 /**
2442 * Render a single plan card.
2443 *
2444 * @param array<string, mixed> $plan Plan data.
2445 * @param array<int, array<string, mixed>> $features Feature list.
2446 * @param int $index Plan index starting at 0.
2447 */
2448 protected function render_card(array $plan, array $features, int $index): void
2449 {
2450 $badge = trim((string) ($plan['badge'] ?? ''));
2451 $title = trim((string) ($plan['name'] ?? ''));
2452 $description = trim((string) ($plan['description'] ?? ''));
2453 $price = $plan['price'] ?? '';
2454 $subtitle = trim((string) ($plan['subtitle'] ?? ''));
2455 $footer_note = trim((string) ($plan['footer_note'] ?? ''));
2456
2457 $button_label = trim((string) ($plan['cta_label'] ?? ''));
2458 $button_link = $plan['cta_link'] ?? [];
2459 $button_attributes = $this->get_link_attributes($button_link);
2460
2461 ?>
2462 <div class="king-addons-comparison-matrix-cards__card">
2463 <div class="king-addons-comparison-matrix-cards__header">
2464 <?php if ($badge !== '') : ?>
2465 <span class="king-addons-comparison-matrix-cards__badge">
2466 <?php echo esc_html($badge); ?>
2467 </span>
2468 <?php endif; ?>
2469 <?php if ($title !== '') : ?>
2470 <h3 class="king-addons-comparison-matrix-cards__title">
2471 <?php echo esc_html($title); ?>
2472 </h3>
2473 <?php endif; ?>
2474 <?php if ($description !== '') : ?>
2475 <p class="king-addons-comparison-matrix-cards__description">
2476 <?php echo esc_html($description); ?>
2477 </p>
2478 <?php endif; ?>
2479 <?php if (!empty($price)) : ?>
2480 <div class="king-addons-comparison-matrix-cards__price">
2481 <?php echo wp_kses_post($price); ?>
2482 </div>
2483 <?php endif; ?>
2484 <?php if ($subtitle !== '') : ?>
2485 <div class="king-addons-comparison-matrix-cards__subtitle">
2486 <?php echo esc_html($subtitle); ?>
2487 </div>
2488 <?php endif; ?>
2489 <?php if ($button_label !== '' && !empty($button_link['url'])) : ?>
2490 <a class="king-addons-comparison-matrix-cards__button" <?php echo $button_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
2491 <?php echo esc_html($button_label); ?>
2492 </a>
2493 <?php endif; ?>
2494 </div>
2495 <?php if (!empty($features)) : ?>
2496 <div class="king-addons-comparison-matrix-cards__features">
2497 <ul class="king-addons-comparison-matrix-cards__feature-list">
2498 <?php $this->render_feature_rows($features, $index); ?>
2499 </ul>
2500 </div>
2501 <?php endif; ?>
2502 <?php if ($footer_note !== '') : ?>
2503 <div class="king-addons-comparison-matrix-cards__footer">
2504 <?php echo esc_html($footer_note); ?>
2505 </div>
2506 <?php endif; ?>
2507 </div>
2508 <?php
2509 }
2510
2511 /**
2512 * Render all feature rows for a card.
2513 *
2514 * @param array<int, array<string, mixed>> $features Feature list.
2515 * @param int $index Plan index.
2516 */
2517 protected function render_feature_rows(array $features, int $index): void
2518 {
2519 $previous_group = '';
2520
2521 foreach ($features as $feature) {
2522 $label = trim((string) ($feature['label'] ?? ''));
2523 $tooltip = trim((string) ($feature['tooltip'] ?? ''));
2524 $group = trim((string) ($feature['group'] ?? ''));
2525 $feature_id = (string) ($feature['id'] ?? $label);
2526
2527 if ($group !== '' && $group !== $previous_group) {
2528 $previous_group = $group;
2529 ?>
2530 <li class="king-addons-comparison-matrix-cards__feature-group" data-feature-group="<?php echo esc_attr($group); ?>" role="presentation">
2531 <span class="king-addons-comparison-matrix-cards__group">
2532 <?php echo esc_html($group); ?>
2533 </span>
2534 </li>
2535 <?php
2536 }
2537
2538 if ($label === '') {
2539 continue;
2540 }
2541
2542 $values = $feature['values'] ?? [];
2543 $value = $values[$index] ?? [
2544 'type' => 'excluded',
2545 'text' => '',
2546 ];
2547
2548 $value_type = $value['type'] ?? 'excluded';
2549 $value_text = $value['text'] ?? '';
2550
2551 $item_attributes = $this->render_attributes([
2552 'data-feature-id' => $feature_id,
2553 'data-feature-label' => $label,
2554 'data-feature-tooltip' => $tooltip,
2555 'data-feature-group' => $group,
2556 'data-value-type' => $value_type,
2557 'data-value-text' => $value_text,
2558 ]);
2559 ?>
2560 <li class="king-addons-comparison-matrix-cards__feature" <?php echo $item_attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
2561 <div class="king-addons-comparison-matrix-cards__feature-label">
2562 <span><?php echo esc_html($label); ?></span>
2563 <?php if ($tooltip !== '') : ?>
2564 <?php $tooltip_id = $this->get_tooltip_id($feature_id); ?>
2565 <span class="king-addons-comparison-matrix-cards__tooltip" tabindex="0" aria-describedby="<?php echo esc_attr($tooltip_id); ?>">
2566 <?php $this->render_tooltip_icon(); ?>
2567 <span class="king-addons-comparison-matrix-cards__tooltip-text" role="tooltip" id="<?php echo esc_attr($tooltip_id); ?>">
2568 <?php echo esc_html($tooltip); ?>
2569 </span>
2570 </span>
2571 <?php endif; ?>
2572 </div>
2573 <div class="king-addons-comparison-matrix-cards__feature-value">
2574 <?php if ($value_type === 'text') : ?>
2575 <?php echo esc_html($value_text !== '' ? $value_text : '-'); ?>
2576 <?php else : ?>
2577 <?php $this->render_feature_icon($value_type); ?>
2578 <?php endif; ?>
2579 </div>
2580 </li>
2581 <?php
2582 }
2583 }
2584
2585 /**
2586 * Render feature icon by type.
2587 *
2588 * @param string $type Feature type.
2589 */
2590 protected function render_feature_icon(string $type): void
2591 {
2592 $label = 'Included';
2593 $class = 'is-included';
2594 $path = 'M4 10l4 4 8-8';
2595
2596 if ($type === 'excluded') {
2597 $label = 'Excluded';
2598 $class = 'is-excluded';
2599 $path = 'M4 4l8 8M12 4l-8 8';
2600 } elseif ($type === 'limited') {
2601 $label = 'Limited';
2602 $class = 'is-limited';
2603 $path = 'M4 8h8';
2604 }
2605
2606 ?>
2607 <span class="king-addons-comparison-matrix-cards__icon <?php echo esc_attr($class); ?>" role="img" aria-label="<?php echo esc_attr($label); ?>">
2608 <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
2609 <path d="<?php echo esc_attr($path); ?>" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
2610 </svg>
2611 </span>
2612 <?php
2613 }
2614
2615 /**
2616 * Render tooltip icon.
2617 */
2618 protected function render_tooltip_icon(): void
2619 {
2620 ?>
2621 <span class="king-addons-comparison-matrix-cards__icon king-addons-comparison-matrix-cards__icon--tooltip" aria-hidden="true">
2622 <svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2623 <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
2624 <path d="M8 7.2v3.2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
2625 <circle cx="8" cy="4.6" r="0.8" fill="currentColor" />
2626 </svg>
2627 </span>
2628 <?php
2629 }
2630
2631 /**
2632 * Generate tooltip id.
2633 *
2634 * @param string $feature_id Feature id.
2635 *
2636 * @return string
2637 */
2638 protected function get_tooltip_id(string $feature_id): string
2639 {
2640 return $this->get_id() . '-tooltip-' . $feature_id;
2641 }
2642
2643 /**
2644 * Build link attributes string.
2645 *
2646 * @param array<string, mixed> $link Link data.
2647 *
2648 * @return string
2649 */
2650 protected function get_link_attributes(array $link): string
2651 {
2652 if (empty($link['url'])) {
2653 return '';
2654 }
2655
2656 $attributes = [
2657 'href' => esc_url($link['url']),
2658 'target' => !empty($link['is_external']) ? '_blank' : '_self',
2659 ];
2660
2661 $rel = [];
2662 if (!empty($link['nofollow'])) {
2663 $rel[] = 'nofollow';
2664 }
2665 if (!empty($link['is_external'])) {
2666 $rel[] = 'noopener';
2667 }
2668 if ($rel) {
2669 $attributes['rel'] = implode(' ', array_unique($rel));
2670 }
2671
2672 return $this->render_attributes($attributes);
2673 }
2674
2675 /**
2676 * Render attributes array to HTML string.
2677 *
2678 * @param array<string, string> $attributes Attributes.
2679 *
2680 * @return string
2681 */
2682 protected function render_attributes(array $attributes): string
2683 {
2684 $output = [];
2685
2686 foreach ($attributes as $key => $value) {
2687 if ($value === '') {
2688 continue;
2689 }
2690 $output[] = esc_attr($key) . '="' . esc_attr($value) . '"';
2691 }
2692
2693 return implode(' ', $output);
2694 }
2695
2696 /**
2697 * Build render data based on source.
2698 *
2699 * @param array<string, mixed> $settings Settings.
2700 *
2701 * @return array<string, mixed>
2702 */
2703 protected function get_render_data(array $settings): array
2704 {
2705 $source = $settings['kng_data_source'] ?? 'manual';
2706 $can_pro = $this->can_use_pro();
2707
2708 if ($source !== 'manual' && !$can_pro) {
2709 return [
2710 'plans' => [],
2711 'features' => [],
2712 'pro_locked' => true,
2713 ];
2714 }
2715
2716 if ($source === 'acf') {
2717 return $this->get_acf_data($settings);
2718 }
2719
2720 if ($source === 'woo') {
2721 return $this->get_woo_data($settings);
2722 }
2723
2724 return $this->get_manual_data($settings);
2725 }
2726
2727 /**
2728 * Get manual data.
2729 *
2730 * @param array<string, mixed> $settings Settings.
2731 *
2732 * @return array<string, mixed>
2733 */
2734 protected function get_manual_data(array $settings): array
2735 {
2736 $plans = $this->get_manual_plans($settings);
2737 $features = [];
2738
2739 $feature_rows = $settings['kng_features'] ?? [];
2740 if (!is_array($feature_rows)) {
2741 $feature_rows = [];
2742 }
2743
2744 foreach ($feature_rows as $feature_row) {
2745 $label = trim((string) ($feature_row['kng_feature_label'] ?? ''));
2746 if ($label === '') {
2747 continue;
2748 }
2749
2750 $values = [];
2751 for ($i = 1; $i <= count($plans); $i++) {
2752 $values[] = $this->get_feature_value($feature_row, $i);
2753 }
2754
2755 $features[] = [
2756 'id' => (string) ($feature_row['_id'] ?? sanitize_title($label)),
2757 'label' => $label,
2758 'tooltip' => trim((string) ($feature_row['kng_feature_tooltip'] ?? '')),
2759 'group' => trim((string) ($feature_row['kng_feature_group'] ?? '')),
2760 'values' => $values,
2761 ];
2762 }
2763
2764 return [
2765 'plans' => $plans,
2766 'features' => $features,
2767 ];
2768 }
2769
2770 /**
2771 * Get manual plans list.
2772 *
2773 * @param array<string, mixed> $settings Settings.
2774 *
2775 * @return array<int, array<string, mixed>>
2776 */
2777 protected function get_manual_plans(array $settings): array
2778 {
2779 $plans = $settings['kng_plans'] ?? [];
2780 if (!is_array($plans)) {
2781 return [];
2782 }
2783
2784 $limit = isset($settings['kng_plans_count']) ? (int) $settings['kng_plans_count'] : 3;
2785 $limit = max(2, min(6, $limit));
2786 if (!$this->can_use_pro()) {
2787 $limit = min(3, $limit);
2788 }
2789
2790 $plans = array_slice($plans, 0, $limit);
2791 $normalized = [];
2792
2793 foreach ($plans as $plan) {
2794 $link = $plan['kng_plan_cta_link'] ?? [];
2795 $normalized[] = [
2796 'name' => $plan['kng_plan_name'] ?? '',
2797 'badge' => $plan['kng_plan_badge'] ?? '',
2798 'description' => $plan['kng_plan_description'] ?? '',
2799 'price' => $plan['kng_plan_price'] ?? '',
2800 'subtitle' => $plan['kng_plan_subtitle'] ?? '',
2801 'cta_label' => $plan['kng_plan_cta_label'] ?? '',
2802 'cta_link' => $this->normalize_link($link, $plan['kng_plan_cta_label'] ?? ''),
2803 'footer_note' => $plan['kng_plan_footer_note'] ?? '',
2804 ];
2805 }
2806
2807 return $normalized;
2808 }
2809
2810 /**
2811 * Get ACF data.
2812 *
2813 * @param array<string, mixed> $settings Settings.
2814 *
2815 * @return array<string, mixed>
2816 */
2817 protected function get_acf_data(array $settings): array
2818 {
2819 if (!function_exists('get_field')) {
2820 return [
2821 'plans' => [],
2822 'features' => [],
2823 ];
2824 }
2825
2826 $post_id = $this->resolve_acf_post_id($settings);
2827 $plans_key = trim((string) ($settings['kng_acf_plans_repeater'] ?? ''));
2828 if ($plans_key === '') {
2829 return [
2830 'plans' => [],
2831 'features' => [],
2832 ];
2833 }
2834
2835 $rows = get_field($plans_key, $post_id);
2836 if (!is_array($rows)) {
2837 return [
2838 'plans' => [],
2839 'features' => [],
2840 ];
2841 }
2842
2843 $limit = $this->get_max_plans_limit($settings);
2844 $rows = array_slice($rows, 0, $limit);
2845
2846 $plans = [];
2847 $features_map = [];
2848 $feature_order = [];
2849
2850 $feature_repeater = trim((string) ($settings['kng_acf_plan_features_repeater'] ?? ''));
2851 $label_field = trim((string) ($settings['kng_acf_feature_label_field'] ?? ''));
2852 $value_field = trim((string) ($settings['kng_acf_feature_value_field'] ?? ''));
2853 $type_field = trim((string) ($settings['kng_acf_feature_value_type_field'] ?? ''));
2854 $tooltip_field = trim((string) ($settings['kng_acf_feature_tooltip_field'] ?? ''));
2855 $group_field = trim((string) ($settings['kng_acf_feature_group_field'] ?? ''));
2856
2857 foreach ($rows as $plan_index => $row) {
2858 if (!is_array($row)) {
2859 continue;
2860 }
2861
2862 $plans[] = $this->map_acf_plan($row, $settings);
2863
2864 if ($feature_repeater === '' || $label_field === '' || $value_field === '') {
2865 continue;
2866 }
2867
2868 $feature_rows = $row[$feature_repeater] ?? [];
2869 if (!is_array($feature_rows)) {
2870 continue;
2871 }
2872
2873 foreach ($feature_rows as $feature_row) {
2874 if (!is_array($feature_row)) {
2875 continue;
2876 }
2877
2878 $label = trim((string) ($feature_row[$label_field] ?? ''));
2879 if ($label === '') {
2880 continue;
2881 }
2882
2883 $group_value = $group_field ? (string) ($feature_row[$group_field] ?? '') : '';
2884 $key_base = $group_value !== '' ? $group_value . '-' . $label : $label;
2885 $key = sanitize_title($key_base);
2886 if ($key === '') {
2887 $key = 'feature-' . md5($key_base);
2888 }
2889 if (!isset($features_map[$key])) {
2890 $features_map[$key] = [
2891 'id' => $key,
2892 'label' => $label,
2893 'tooltip' => $tooltip_field ? (string) ($feature_row[$tooltip_field] ?? '') : '',
2894 'group' => $group_value,
2895 'values' => [],
2896 ];
2897 $feature_order[] = $key;
2898 }
2899
2900 $type_override = $type_field ? (string) ($feature_row[$type_field] ?? '') : '';
2901 $value = $this->normalize_feature_value($feature_row[$value_field] ?? '', $type_override);
2902 $features_map[$key]['values'][$plan_index] = $value;
2903 }
2904 }
2905
2906 $features = [];
2907 foreach ($feature_order as $key) {
2908 $feature = $features_map[$key];
2909 $feature['values'] = $this->fill_feature_values($feature['values'], count($plans));
2910 $features[] = $feature;
2911 }
2912
2913 return [
2914 'plans' => $plans,
2915 'features' => $features,
2916 ];
2917 }
2918
2919 /**
2920 * Resolve ACF post ID.
2921 *
2922 * @param array<string, mixed> $settings Settings.
2923 *
2924 * @return int
2925 */
2926 protected function resolve_acf_post_id(array $settings): int
2927 {
2928 if (($settings['kng_acf_post_source'] ?? 'current') === 'custom') {
2929 return max(1, (int) ($settings['kng_acf_post_id'] ?? 0));
2930 }
2931
2932 return (int) get_the_ID();
2933 }
2934
2935 /**
2936 * Map ACF plan row to plan data.
2937 *
2938 * @param array<string, mixed> $row Plan row.
2939 * @param array<string, mixed> $settings Settings.
2940 *
2941 * @return array<string, mixed>
2942 */
2943 protected function map_acf_plan(array $row, array $settings): array
2944 {
2945 $name_field = trim((string) ($settings['kng_acf_plan_name_field'] ?? ''));
2946 $badge_field = trim((string) ($settings['kng_acf_plan_badge_field'] ?? ''));
2947 $desc_field = trim((string) ($settings['kng_acf_plan_description_field'] ?? ''));
2948 $price_field = trim((string) ($settings['kng_acf_plan_price_field'] ?? ''));
2949 $subtitle_field = trim((string) ($settings['kng_acf_plan_subtitle_field'] ?? ''));
2950 $cta_label_field = trim((string) ($settings['kng_acf_plan_cta_label_field'] ?? ''));
2951 $cta_link_field = trim((string) ($settings['kng_acf_plan_cta_link_field'] ?? ''));
2952 $footer_field = trim((string) ($settings['kng_acf_plan_footer_field'] ?? ''));
2953
2954 $cta_label = $cta_label_field ? (string) ($row[$cta_label_field] ?? '') : '';
2955 $cta_link = $cta_link_field ? ($row[$cta_link_field] ?? []) : [];
2956
2957 $link = $this->normalize_link($cta_link, $cta_label);
2958
2959 return [
2960 'name' => $name_field ? (string) ($row[$name_field] ?? '') : '',
2961 'badge' => $badge_field ? (string) ($row[$badge_field] ?? '') : '',
2962 'description' => $desc_field ? (string) ($row[$desc_field] ?? '') : '',
2963 'price' => $price_field ? (string) ($row[$price_field] ?? '') : '',
2964 'subtitle' => $subtitle_field ? (string) ($row[$subtitle_field] ?? '') : '',
2965 'cta_label' => $cta_label !== '' ? $cta_label : ($link['title'] ?? ''),
2966 'cta_link' => $link,
2967 'footer_note' => $footer_field ? (string) ($row[$footer_field] ?? '') : '',
2968 ];
2969 }
2970
2971 /**
2972 * Get WooCommerce data.
2973 *
2974 * @param array<string, mixed> $settings Settings.
2975 *
2976 * @return array<string, mixed>
2977 */
2978 protected function get_woo_data(array $settings): array
2979 {
2980 if (!class_exists('WooCommerce') || !function_exists('wc_get_product')) {
2981 return [
2982 'plans' => [],
2983 'features' => [],
2984 ];
2985 }
2986
2987 if (($settings['kng_woo_compare_variations'] ?? '') === 'yes') {
2988 $variation_data = $this->get_woo_variations_data($settings);
2989 if (!empty($variation_data['plans'])) {
2990 return $variation_data;
2991 }
2992 }
2993
2994 $products = $this->get_woo_products($settings);
2995 if (count($products) < 2) {
2996 return [
2997 'plans' => [],
2998 'features' => [],
2999 ];
3000 }
3001
3002 $plans = [];
3003 foreach ($products as $product) {
3004 $plans[] = $this->map_woo_plan($product, $settings);
3005 }
3006
3007 $features = $this->get_woo_features($products, $settings);
3008
3009 return [
3010 'plans' => $plans,
3011 'features' => $features,
3012 ];
3013 }
3014
3015 /**
3016 * Get WooCommerce products.
3017 *
3018 * @param array<string, mixed> $settings Settings.
3019 *
3020 * @return array<int, WC_Product>
3021 */
3022 protected function get_woo_products(array $settings): array
3023 {
3024 $source = $settings['kng_woo_products_source'] ?? 'manual';
3025 $limit = $this->get_max_plans_limit($settings, 'kng_woo_products_limit');
3026
3027 $products = [];
3028 if ($source === 'manual') {
3029 $ids = $settings['kng_woo_products'] ?? [];
3030 if (!is_array($ids)) {
3031 $ids = [];
3032 }
3033 foreach ($ids as $id) {
3034 $product = wc_get_product((int) $id);
3035 if ($product) {
3036 $products[] = $product;
3037 }
3038 }
3039 } else {
3040 $current_id = (int) get_the_ID();
3041 $current_product = $current_id ? wc_get_product($current_id) : null;
3042 $ids = [];
3043 if ($current_product) {
3044 if ($source === 'related') {
3045 $ids = wc_get_related_products($current_id, $limit);
3046 } elseif ($source === 'upsell') {
3047 $ids = $current_product->get_upsell_ids();
3048 } elseif ($source === 'cross_sell') {
3049 $ids = $current_product->get_cross_sell_ids();
3050 }
3051 }
3052
3053 $ids = array_slice(array_filter(array_map('intval', $ids)), 0, $limit);
3054 foreach ($ids as $id) {
3055 $product = wc_get_product($id);
3056 if ($product) {
3057 $products[] = $product;
3058 }
3059 }
3060 }
3061
3062 return array_slice($products, 0, $limit);
3063 }
3064
3065 /**
3066 * Get WooCommerce variations data for a single variable product.
3067 *
3068 * @param array<string, mixed> $settings Settings.
3069 *
3070 * @return array<string, mixed>
3071 */
3072 protected function get_woo_variations_data(array $settings): array
3073 {
3074 $ids = $settings['kng_woo_products'] ?? [];
3075 if (!is_array($ids) || empty($ids)) {
3076 return [
3077 'plans' => [],
3078 'features' => [],
3079 ];
3080 }
3081
3082 $ids = array_values(array_filter(array_map('intval', $ids)));
3083 if (empty($ids)) {
3084 return [
3085 'plans' => [],
3086 'features' => [],
3087 ];
3088 }
3089
3090 $product_id = (int) $ids[0];
3091 $product = $product_id ? wc_get_product($product_id) : null;
3092 if (!$product || !$product->is_type('variable')) {
3093 return [
3094 'plans' => [],
3095 'features' => [],
3096 ];
3097 }
3098
3099 $limit = $this->get_max_plans_limit($settings, 'kng_woo_products_limit');
3100 $variation_ids = array_slice($product->get_children(), 0, $limit);
3101 $variations = [];
3102
3103 foreach ($variation_ids as $variation_id) {
3104 $variation = wc_get_product((int) $variation_id);
3105 if ($variation) {
3106 $variations[] = $variation;
3107 }
3108 }
3109
3110 if (count($variations) < 2) {
3111 return [
3112 'plans' => [],
3113 'features' => [],
3114 ];
3115 }
3116
3117 $plans = [];
3118 foreach ($variations as $variation) {
3119 $plans[] = $this->map_woo_variation_plan($variation, $product, $settings);
3120 }
3121
3122 return [
3123 'plans' => $plans,
3124 'features' => $this->get_woo_features($variations, $settings),
3125 ];
3126 }
3127
3128 /**
3129 * Map WooCommerce product to plan data.
3130 *
3131 * @param WC_Product $product Product.
3132 * @param array<string, mixed> $settings Settings.
3133 *
3134 * @return array<string, mixed>
3135 */
3136 protected function map_woo_plan(WC_Product $product, array $settings): array
3137 {
3138 $show_price = ($settings['kng_woo_show_price'] ?? '') === 'yes';
3139 $cta_mode = $settings['kng_woo_cta_mode'] ?? 'product';
3140 $cta_label = $settings['kng_woo_cta_label'] ?? esc_html__('View Product', 'king-addons');
3141
3142 $link = [
3143 'url' => '',
3144 'is_external' => false,
3145 'nofollow' => false,
3146 'title' => '',
3147 ];
3148
3149 if ($cta_mode === 'add_to_cart') {
3150 $link['url'] = $product->add_to_cart_url();
3151 } else {
3152 $link['url'] = get_permalink($product->get_id());
3153 }
3154
3155 return [
3156 'name' => $product->get_name(),
3157 'badge' => '',
3158 'description' => '',
3159 'price' => $show_price ? $product->get_price_html() : '',
3160 'subtitle' => '',
3161 'cta_label' => $cta_label,
3162 'cta_link' => $link,
3163 'footer_note' => '',
3164 ];
3165 }
3166
3167 /**
3168 * Map WooCommerce variation to plan data.
3169 *
3170 * @param WC_Product $variation Variation product.
3171 * @param WC_Product $parent Parent product.
3172 * @param array<string, mixed> $settings Settings.
3173 *
3174 * @return array<string, mixed>
3175 */
3176 protected function map_woo_variation_plan(WC_Product $variation, WC_Product $parent, array $settings): array
3177 {
3178 $show_price = ($settings['kng_woo_show_price'] ?? '') === 'yes';
3179 $cta_mode = $settings['kng_woo_cta_mode'] ?? 'product';
3180 $cta_label = $settings['kng_woo_cta_label'] ?? esc_html__('View Product', 'king-addons');
3181 $name_mode = $settings['kng_woo_variation_title_mode'] ?? 'full';
3182 $subtitle_mode = $settings['kng_woo_variation_subtitle_mode'] ?? 'none';
3183
3184 $link = [
3185 'url' => '',
3186 'is_external' => false,
3187 'nofollow' => false,
3188 'title' => '',
3189 ];
3190
3191 if ($cta_mode === 'add_to_cart') {
3192 $link['url'] = $variation->add_to_cart_url();
3193 } else {
3194 $link['url'] = $this->get_variation_permalink($variation, $parent);
3195 }
3196
3197 $parent_name = $parent->get_name();
3198 $variation_name = $variation->get_formatted_name();
3199 if ($variation_name === '') {
3200 $variation_name = $parent_name;
3201 }
3202
3203 $variation_attributes = $this->format_variation_attributes($variation);
3204 $name = $variation_name;
3205
3206 if ($name_mode === 'attributes') {
3207 $name = $variation_attributes !== '' ? $variation_attributes : $parent_name;
3208 } elseif ($name_mode === 'parent') {
3209 $name = $parent_name;
3210 }
3211
3212 $subtitle = '';
3213 if ($subtitle_mode === 'attributes') {
3214 $subtitle = $variation_attributes;
3215 }
3216
3217 if ($subtitle !== '' && $subtitle === $name) {
3218 $subtitle = '';
3219 }
3220
3221 return [
3222 'name' => $name,
3223 'badge' => '',
3224 'description' => '',
3225 'price' => $show_price ? $variation->get_price_html() : '',
3226 'subtitle' => $subtitle,
3227 'cta_label' => $cta_label,
3228 'cta_link' => $link,
3229 'footer_note' => '',
3230 ];
3231 }
3232
3233 /**
3234 * Build variation permalink with preselected attributes.
3235 *
3236 * @param WC_Product $variation Variation product.
3237 * @param WC_Product $parent Parent product.
3238 *
3239 * @return string
3240 */
3241 protected function get_variation_permalink(WC_Product $variation, WC_Product $parent): string
3242 {
3243 $permalink = get_permalink($parent->get_id());
3244 if (!$permalink) {
3245 return '';
3246 }
3247
3248 if (!method_exists($variation, 'get_variation_attributes')) {
3249 return $permalink;
3250 }
3251
3252 $attributes = $variation->get_variation_attributes();
3253 if (!is_array($attributes) || empty($attributes)) {
3254 return $permalink;
3255 }
3256
3257 $query_args = [];
3258 foreach ($attributes as $key => $value) {
3259 $value = is_array($value) ? implode(', ', $value) : (string) $value;
3260 if ($value === '') {
3261 continue;
3262 }
3263 $query_args[(string) $key] = $value;
3264 }
3265
3266 if (empty($query_args)) {
3267 return $permalink;
3268 }
3269
3270 $query_args['variation_id'] = (string) $variation->get_id();
3271
3272 return add_query_arg($query_args, $permalink);
3273 }
3274
3275 /**
3276 * Format variation attributes for labels or subtitles.
3277 *
3278 * @param WC_Product $variation Variation product.
3279 *
3280 * @return string
3281 */
3282 protected function format_variation_attributes(WC_Product $variation): string
3283 {
3284 if (!method_exists($variation, 'get_variation_attributes')) {
3285 return '';
3286 }
3287
3288 $attributes = $variation->get_variation_attributes();
3289 if (!is_array($attributes) || empty($attributes)) {
3290 return '';
3291 }
3292
3293 $parts = [];
3294 foreach ($attributes as $key => $value) {
3295 $value = is_array($value) ? implode(', ', $value) : (string) $value;
3296 if ($value === '') {
3297 continue;
3298 }
3299
3300 $taxonomy = str_replace('attribute_', '', (string) $key);
3301 $label = '';
3302 if ($taxonomy !== '') {
3303 if (function_exists('wc_attribute_label')) {
3304 $label = wc_attribute_label($taxonomy);
3305 }
3306 if ($label === '') {
3307 $label = ucwords(str_replace(['pa_', '-', '_'], ' ', $taxonomy));
3308 }
3309 }
3310
3311 $value_label = $value;
3312 if ($taxonomy !== '' && taxonomy_exists($taxonomy)) {
3313 $term = get_term_by('slug', $value, $taxonomy);
3314 if ($term && !is_wp_error($term)) {
3315 $value_label = $term->name;
3316 }
3317 }
3318
3319 if ($label !== '') {
3320 $parts[] = $label . ': ' . $value_label;
3321 } else {
3322 $parts[] = $value_label;
3323 }
3324 }
3325
3326 return implode(', ', $parts);
3327 }
3328
3329 /**
3330 * Build feature list from WooCommerce attributes.
3331 *
3332 * @param array<int, WC_Product> $products Products.
3333 * @param array<string, mixed> $settings Settings.
3334 *
3335 * @return array<int, array<string, mixed>>
3336 */
3337 protected function get_woo_features(array $products, array $settings): array
3338 {
3339 $attributes = $settings['kng_woo_attributes'] ?? [];
3340 if (!is_array($attributes)) {
3341 return [];
3342 }
3343
3344 $features = [];
3345 foreach ($attributes as $attribute) {
3346 $slug = trim((string) ($attribute['kng_woo_attr_slug'] ?? ''));
3347 if ($slug === '') {
3348 continue;
3349 }
3350
3351 $label_override = trim((string) ($attribute['kng_woo_attr_label'] ?? ''));
3352 $label = $label_override;
3353 if ($label === '') {
3354 if (function_exists('wc_attribute_label')) {
3355 $label = wc_attribute_label($slug);
3356 } else {
3357 $label = $slug;
3358 }
3359 }
3360
3361 $group = trim((string) ($attribute['kng_woo_attr_group'] ?? ''));
3362 $tooltip = trim((string) ($attribute['kng_woo_attr_tooltip'] ?? ''));
3363 $fallback = trim((string) ($attribute['kng_woo_attr_fallback'] ?? ''));
3364 $id_base = $group !== '' ? $group . '-' . $slug : $slug;
3365 $feature_id = sanitize_title($id_base);
3366 if ($feature_id === '') {
3367 $feature_id = 'feature-' . md5($id_base);
3368 }
3369
3370 $values = [];
3371 foreach ($products as $product) {
3372 $raw = $this->get_product_attribute_value($product, $slug, $fallback);
3373 $values[] = $this->normalize_feature_value($raw);
3374 }
3375
3376 $features[] = [
3377 'id' => $feature_id,
3378 'label' => $label,
3379 'tooltip' => $tooltip,
3380 'group' => $group,
3381 'values' => $values,
3382 ];
3383 }
3384
3385 return $features;
3386 }
3387
3388 /**
3389 * Get product attribute value with fallback.
3390 *
3391 * @param WC_Product $product Product.
3392 * @param string $slug Attribute slug.
3393 * @param string $fallback Fallback meta key.
3394 *
3395 * @return mixed
3396 */
3397 protected function get_product_attribute_value(WC_Product $product, string $slug, string $fallback)
3398 {
3399 $value = '';
3400
3401 if (taxonomy_exists($slug) && !$product->is_type('variation')) {
3402 $terms = wc_get_product_terms($product->get_id(), $slug, ['fields' => 'names']);
3403 if (!empty($terms) && is_array($terms)) {
3404 $value = implode(', ', $terms);
3405 }
3406 } else {
3407 $value = $product->get_attribute($slug);
3408 }
3409
3410 if ($value === '' && $product->is_type('variation')) {
3411 $parent = wc_get_product($product->get_parent_id());
3412 if ($parent) {
3413 if (taxonomy_exists($slug)) {
3414 $terms = wc_get_product_terms($parent->get_id(), $slug, ['fields' => 'names']);
3415 if (!empty($terms) && is_array($terms)) {
3416 $value = implode(', ', $terms);
3417 }
3418 } else {
3419 $value = $parent->get_attribute($slug);
3420 }
3421 }
3422 }
3423
3424 if ($value === '' && $fallback !== '') {
3425 $meta_value = get_post_meta($product->get_id(), $fallback, true);
3426 if ($meta_value !== '') {
3427 $value = $meta_value;
3428 }
3429 }
3430
3431 return $value;
3432 }
3433
3434 /**
3435 * Normalize feature value for rendering.
3436 *
3437 * @param mixed $value Raw value.
3438 * @param string $type_override Optional type.
3439 *
3440 * @return array{type: string, text: string}
3441 */
3442 protected function normalize_feature_value($value, string $type_override = ''): array
3443 {
3444 $type_override = strtolower(trim($type_override));
3445 if ($type_override !== '') {
3446 $type_override = $this->sanitize_feature_type($type_override);
3447 }
3448
3449 if ($type_override !== '') {
3450 return [
3451 'type' => $type_override,
3452 'text' => $type_override === 'text' ? (string) $value : '',
3453 ];
3454 }
3455
3456 if (is_bool($value)) {
3457 return [
3458 'type' => $value ? 'included' : 'excluded',
3459 'text' => '',
3460 ];
3461 }
3462
3463 if (is_array($value)) {
3464 $value = implode(', ', array_map('strval', $value));
3465 }
3466
3467 $value = trim((string) $value);
3468 if ($value === '') {
3469 return [
3470 'type' => 'excluded',
3471 'text' => '',
3472 ];
3473 }
3474
3475 $normalized = strtolower($value);
3476 $included_values = ['yes', 'true', 'included', 'available', 'check'];
3477 $excluded_values = ['no', 'false', 'excluded', 'not available', 'x', '-'];
3478 $limited_values = ['limited', 'partial', 'some'];
3479
3480 if (in_array($normalized, $included_values, true)) {
3481 return [
3482 'type' => 'included',
3483 'text' => '',
3484 ];
3485 }
3486
3487 if (in_array($normalized, $excluded_values, true)) {
3488 return [
3489 'type' => 'excluded',
3490 'text' => '',
3491 ];
3492 }
3493
3494 if (in_array($normalized, $limited_values, true)) {
3495 return [
3496 'type' => 'limited',
3497 'text' => '',
3498 ];
3499 }
3500
3501 return [
3502 'type' => 'text',
3503 'text' => $value,
3504 ];
3505 }
3506
3507 /**
3508 * Sanitize feature type.
3509 *
3510 * @param string $type Type.
3511 *
3512 * @return string
3513 */
3514 protected function sanitize_feature_type(string $type): string
3515 {
3516 $allowed = ['included', 'excluded', 'limited', 'text'];
3517 return in_array($type, $allowed, true) ? $type : '';
3518 }
3519
3520 /**
3521 * Get manual feature value for plan index.
3522 *
3523 * @param array<string, mixed> $feature Feature data.
3524 * @param int $index Plan index (1-based).
3525 *
3526 * @return array{type: string, text: string}
3527 */
3528 protected function get_feature_value(array $feature, int $index): array
3529 {
3530 $type_key = 'kng_feature_value_' . $index . '_type';
3531 $text_key = 'kng_feature_value_' . $index . '_text';
3532
3533 $type = (string) ($feature[$type_key] ?? 'excluded');
3534 $type = in_array($type, ['included', 'excluded', 'limited', 'text'], true) ? $type : 'excluded';
3535 $text = trim((string) ($feature[$text_key] ?? ''));
3536
3537 return [
3538 'type' => $type,
3539 'text' => $text,
3540 ];
3541 }
3542
3543 /**
3544 * Fill missing feature values with defaults.
3545 *
3546 * @param array<int, array{type: string, text: string}> $values Values.
3547 * @param int $count Plan count.
3548 *
3549 * @return array<int, array{type: string, text: string}>
3550 */
3551 protected function fill_feature_values(array $values, int $count): array
3552 {
3553 for ($i = 0; $i < $count; $i++) {
3554 if (!isset($values[$i])) {
3555 $values[$i] = [
3556 'type' => 'excluded',
3557 'text' => '',
3558 ];
3559 }
3560 }
3561
3562 ksort($values);
3563
3564 return array_values($values);
3565 }
3566
3567 /**
3568 * Normalize link data.
3569 *
3570 * @param mixed $link Link data.
3571 * @param string $label_fallback Label fallback.
3572 *
3573 * @return array<string, mixed>
3574 */
3575 protected function normalize_link($link, string $label_fallback = ''): array
3576 {
3577 if (is_string($link)) {
3578 $link = [
3579 'url' => $link,
3580 ];
3581 }
3582
3583 if (!is_array($link)) {
3584 $link = [];
3585 }
3586
3587 return [
3588 'url' => $link['url'] ?? '',
3589 'is_external' => !empty($link['target']) || !empty($link['is_external']),
3590 'nofollow' => !empty($link['nofollow']),
3591 'title' => $link['title'] ?? $label_fallback,
3592 ];
3593 }
3594
3595 /**
3596 * Get max plan limit based on settings.
3597 *
3598 * @param array<string, mixed> $settings Settings.
3599 * @param string $key Optional key.
3600 *
3601 * @return int
3602 */
3603 protected function get_max_plans_limit(array $settings, string $key = 'kng_max_plans'): int
3604 {
3605 $limit = isset($settings[$key]) ? (int) $settings[$key] : 4;
3606 $limit = max(2, min(8, $limit));
3607 if (!$this->can_use_pro()) {
3608 $limit = min(3, $limit);
3609 }
3610 return $limit;
3611 }
3612
3613 /**
3614 * Check if we are in Elementor editor mode.
3615 */
3616 protected function is_editor_mode(): bool
3617 {
3618 return class_exists(Plugin::class) && Plugin::$instance->editor->is_edit_mode();
3619 }
3620
3621 /**
3622 * Check if Pro is available.
3623 */
3624 protected function can_use_pro(): bool
3625 {
3626 return function_exists('king_addons_can_use_pro') && king_addons_can_use_pro();
3627 }
3628
3629 /**
3630 * Get Pro label helper.
3631 *
3632 * @param string $label Label.
3633 *
3634 * @return string
3635 */
3636 protected function get_pro_label(string $label): string
3637 {
3638 if ($this->can_use_pro()) {
3639 return $label;
3640 }
3641
3642 return $label . ' <i class="eicon-pro-icon"></i>';
3643 }
3644
3645 /**
3646 * Get pro control class helper.
3647 */
3648 protected function get_pro_control_class(): string
3649 {
3650 return $this->can_use_pro() ? '' : 'king-addons-pro-control';
3651 }
3652 }
3653