PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / widgets / 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.49, at includes/widgets/Comparison_Matrix_Cards/Comparison_Matrix_Cards.php

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