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 / Quick_Card_Grid / Quick_Card_Grid.php

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

1,012 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Quick Card Grid Widget (Free)
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Border;
12 use Elementor\Group_Control_Box_Shadow;
13 use Elementor\Group_Control_Image_Size;
14 use Elementor\Group_Control_Typography;
15 use Elementor\Repeater;
16 use Elementor\Utils;
17 use Elementor\Widget_Base;
18
19 if (!defined('ABSPATH')) {
20 exit;
21 }
22
23 /**
24 * Renders the Quick Card Grid widget.
25 */
26 class Quick_Card_Grid extends Widget_Base
27 {
28 /**
29 * Get widget slug.
30 *
31 * @return string
32 */
33 public function get_name(): string
34 {
35 return 'king-addons-quick-card-grid';
36 }
37
38 /**
39 * Get widget title.
40 *
41 * @return string
42 */
43 public function get_title(): string
44 {
45 return esc_html__('Quick Card Grid', 'king-addons');
46 }
47
48 /**
49 * Get widget icon class.
50 *
51 * @return string
52 */
53 public function get_icon(): string
54 {
55 return 'king-addons-icon king-addons-simple-card-grid';
56 }
57
58 /**
59 * Enqueue script dependencies.
60 *
61 * @return array<int, string>
62 */
63 public function get_script_depends(): array
64 {
65 return [
66 KING_ADDONS_ASSETS_UNIQUE_KEY . '-quick-card-grid-script',
67 ];
68 }
69
70 /**
71 * Enqueue style dependencies.
72 *
73 * @return array<int, string>
74 */
75 public function get_style_depends(): array
76 {
77 return [
78 KING_ADDONS_ASSETS_UNIQUE_KEY . '-quick-card-grid-style',
79 ];
80 }
81
82 /**
83 * Get widget categories.
84 *
85 * @return array<int, string>
86 */
87 public function get_categories(): array
88 {
89 return ['king-addons'];
90 }
91
92 /**
93 * Get widget keywords.
94 *
95 * @return array<int, string>
96 */
97 public function get_keywords(): array
98 {
99 return ['grid', 'card', 'content', 'king-addons'];
100 }
101
102 /**
103 * Register widget controls.
104 *
105 * @return void
106 */
107 public function register_controls(): void
108 {
109 $this->register_content_controls();
110 $this->register_layout_controls();
111 $this->register_style_card_controls();
112 $this->register_style_text_controls();
113 $this->register_style_button_controls();
114 $this->register_pro_notice_controls();
115 }
116
117 /**
118 * Render widget output.
119 *
120 * @return void
121 */
122 public function render(): void
123 {
124 $settings = $this->get_settings_for_display();
125 $this->render_output($settings);
126 }
127
128 /**
129 * Render grid output for the frontend.
130 *
131 * @param array<string, mixed> $settings Widget settings.
132 *
133 * @return void
134 */
135 public function render_output(array $settings): void
136 {
137 $cards = $settings['kng_cards'] ?? [];
138 if (empty($cards)) {
139 return;
140 }
141
142 $wrapper_classes = $this->get_wrapper_classes($settings);
143 $wrapper_style = $this->get_wrapper_style($settings);
144
145 ?>
146 <div class="<?php echo esc_attr(implode(' ', $wrapper_classes)); ?>"<?php echo $wrapper_style; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
147 <div class="king-addons-card-grid__grid">
148 <?php foreach ($cards as $card) : ?>
149 <?php $this->render_card($settings, $card); ?>
150 <?php endforeach; ?>
151 </div>
152 </div>
153 <?php
154 }
155
156 protected function register_content_controls(): void
157 {
158 $this->start_controls_section(
159 'kng_content_section',
160 [
161 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Cards', 'king-addons'),
162 'tab' => Controls_Manager::TAB_CONTENT,
163 ]
164 );
165
166 $repeater = new Repeater();
167
168 $repeater->add_control(
169 'kng_card_image',
170 [
171 'label' => esc_html__('Image', 'king-addons'),
172 'type' => Controls_Manager::MEDIA,
173 'default' => [
174 'url' => Utils::get_placeholder_image_src(),
175 ],
176 ]
177 );
178
179 $repeater->add_control(
180 'kng_card_title',
181 [
182 'label' => esc_html__('Title', 'king-addons'),
183 'type' => Controls_Manager::TEXT,
184 'default' => esc_html__('Card title', 'king-addons'),
185 'label_block' => true,
186 ]
187 );
188
189 $repeater->add_control(
190 'kng_card_description',
191 [
192 'label' => esc_html__('Description', 'king-addons'),
193 'type' => Controls_Manager::TEXTAREA,
194 'rows' => 3,
195 'default' => esc_html__('Card description goes here.', 'king-addons'),
196 ]
197 );
198
199 $repeater->add_control(
200 'kng_card_button_text',
201 [
202 'label' => esc_html__('Button Text', 'king-addons'),
203 'type' => Controls_Manager::TEXT,
204 'default' => esc_html__('Learn more', 'king-addons'),
205 'label_block' => true,
206 ]
207 );
208
209 $repeater->add_control(
210 'kng_card_button_link',
211 [
212 'label' => esc_html__('Button Link', 'king-addons'),
213 'type' => Controls_Manager::URL,
214 'placeholder' => esc_html__('https://your-link.com', 'king-addons'),
215 ]
216 );
217
218 $repeater->add_control(
219 'kng_card_link_enable',
220 [
221 'label' => esc_html__('Make Whole Card Clickable', 'king-addons'),
222 'type' => Controls_Manager::SWITCHER,
223 'label_on' => esc_html__('Yes', 'king-addons'),
224 'label_off' => esc_html__('No', 'king-addons'),
225 'return_value' => 'yes',
226 'description' => esc_html__('Editor safety: the card link is disabled in the Elementor editor to prevent accidental navigation.', 'king-addons'),
227 ]
228 );
229
230 $repeater->add_control(
231 'kng_card_link',
232 [
233 'label' => esc_html__('Card Link', 'king-addons'),
234 'type' => Controls_Manager::URL,
235 'placeholder' => esc_html__('https://your-link.com', 'king-addons'),
236 'condition' => [
237 'kng_card_link_enable' => 'yes',
238 ],
239 ]
240 );
241
242 $this->add_group_control(
243 Group_Control_Image_Size::get_type(),
244 [
245 'name' => 'kng_card_image',
246 'default' => 'medium',
247 'separator' => 'before',
248 ]
249 );
250
251 $this->add_control(
252 'kng_cards',
253 [
254 'label' => esc_html__('Cards', 'king-addons'),
255 'type' => Controls_Manager::REPEATER,
256 'fields' => $repeater->get_controls(),
257 'default' => [
258 [
259 'kng_card_title' => esc_html__('Modern layout', 'king-addons'),
260 'kng_card_description' => esc_html__('Clean card grid with safe spacing.', 'king-addons'),
261 'kng_card_button_text' => esc_html__('Read more', 'king-addons'),
262 'kng_card_image' => ['url' => Utils::get_placeholder_image_src()],
263 ],
264 [
265 'kng_card_title' => esc_html__('Responsive by default', 'king-addons'),
266 'kng_card_description' => esc_html__('Columns adapt to devices.', 'king-addons'),
267 'kng_card_button_text' => esc_html__('Details', 'king-addons'),
268 'kng_card_image' => ['url' => Utils::get_placeholder_image_src()],
269 ],
270 [
271 'kng_card_title' => esc_html__('Ready to use', 'king-addons'),
272 'kng_card_description' => esc_html__('Balanced paddings and typography.', 'king-addons'),
273 'kng_card_button_text' => esc_html__('Explore', 'king-addons'),
274 'kng_card_image' => ['url' => Utils::get_placeholder_image_src()],
275 ],
276 ],
277 'title_field' => '{{{ kng_card_title }}}',
278 ]
279 );
280
281 $this->end_controls_section();
282 }
283
284 protected function register_layout_controls(): void
285 {
286 $this->start_controls_section(
287 'kng_layout_section',
288 [
289 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
290 'tab' => Controls_Manager::TAB_CONTENT,
291 ]
292 );
293
294 $this->add_responsive_control(
295 'kng_columns',
296 [
297 'label' => esc_html__('Columns', 'king-addons'),
298 'type' => Controls_Manager::NUMBER,
299 'min' => 1,
300 'max' => 6,
301 'step' => 1,
302 'default' => 3,
303 'tablet_default' => 2,
304 'mobile_default' => 1,
305 'selectors' => [
306 '{{WRAPPER}} .king-addons-card-grid__grid' => 'grid-template-columns: repeat({{VALUE}}, minmax(0, 1fr));',
307 ],
308 ]
309 );
310
311 $this->add_responsive_control(
312 'kng_grid_gap',
313 [
314 'label' => esc_html__('Gap', 'king-addons'),
315 'type' => Controls_Manager::SLIDER,
316 'size_units' => ['px'],
317 'range' => [
318 'px' => [
319 'min' => 0,
320 'max' => 80,
321 ],
322 ],
323 'default' => [
324 'size' => 20,
325 'unit' => 'px',
326 ],
327 'selectors' => [
328 '{{WRAPPER}} .king-addons-card-grid__grid' => 'gap: {{SIZE}}{{UNIT}};',
329 ],
330 ]
331 );
332
333 $this->end_controls_section();
334 }
335
336 protected function register_style_card_controls(): void
337 {
338 $this->start_controls_section(
339 'kng_style_card_section',
340 [
341 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Card', 'king-addons'),
342 'tab' => Controls_Manager::TAB_STYLE,
343 ]
344 );
345
346 $this->add_control(
347 'kng_card_background',
348 [
349 'label' => esc_html__('Background', 'king-addons'),
350 'type' => Controls_Manager::COLOR,
351 'selectors' => [
352 '{{WRAPPER}} .king-addons-card-grid__card' => 'background-color: {{VALUE}};',
353 ],
354 ]
355 );
356
357 $this->add_group_control(
358 Group_Control_Border::get_type(),
359 [
360 'name' => 'kng_card_border',
361 'selector' => '{{WRAPPER}} .king-addons-card-grid__card',
362 ]
363 );
364
365 $this->add_control(
366 'kng_card_radius',
367 [
368 'label' => esc_html__('Border Radius', 'king-addons'),
369 'type' => Controls_Manager::SLIDER,
370 'size_units' => ['px', '%'],
371 'range' => [
372 'px' => ['min' => 0, 'max' => 60],
373 '%' => ['min' => 0, 'max' => 100],
374 ],
375 'selectors' => [
376 '{{WRAPPER}} .king-addons-card-grid__card' => 'border-radius: {{SIZE}}{{UNIT}};',
377 ],
378 ]
379 );
380
381 $this->add_group_control(
382 Group_Control_Box_Shadow::get_type(),
383 [
384 'name' => 'kng_card_shadow',
385 'selector' => '{{WRAPPER}} .king-addons-card-grid__card',
386 ]
387 );
388
389 $this->add_control(
390 'kng_card_shadow_disable',
391 [
392 'label' => esc_html__('Disable Box Shadow', 'king-addons'),
393 'type' => Controls_Manager::SWITCHER,
394 'return_value' => 'yes',
395 'default' => '',
396 'selectors' => [
397 '{{WRAPPER}} .king-addons-card-grid__card' => 'box-shadow: none !important;',
398 ],
399 ]
400 );
401
402 $this->add_control(
403 'kng_card_shadow_hover_disable',
404 [
405 'label' => esc_html__('Disable Box Shadow on Hover', 'king-addons'),
406 'type' => Controls_Manager::SWITCHER,
407 'return_value' => 'yes',
408 'default' => '',
409 'selectors' => [
410 '{{WRAPPER}} .king-addons-card-grid__card:hover' => 'box-shadow: none !important;',
411 ],
412 ]
413 );
414
415 $this->add_responsive_control(
416 'kng_card_padding',
417 [
418 'label' => esc_html__('Card Padding', 'king-addons'),
419 'type' => Controls_Manager::DIMENSIONS,
420 'size_units' => ['px', '%', 'em'],
421 'selectors' => [
422 '{{WRAPPER}} .king-addons-card-grid__card' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
423 ],
424 ]
425 );
426
427 $this->end_controls_section();
428 }
429
430 protected function register_style_text_controls(): void
431 {
432 $this->start_controls_section(
433 'kng_style_text_section',
434 [
435 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Text', 'king-addons'),
436 'tab' => Controls_Manager::TAB_STYLE,
437 ]
438 );
439
440 $this->add_group_control(
441 Group_Control_Typography::get_type(),
442 [
443 'name' => 'kng_title_typography',
444 'selector' => '{{WRAPPER}} .king-addons-card-grid__title',
445 ]
446 );
447
448 $this->add_control(
449 'kng_title_color',
450 [
451 'label' => esc_html__('Title Color', 'king-addons'),
452 'type' => Controls_Manager::COLOR,
453 'selectors' => [
454 '{{WRAPPER}} .king-addons-card-grid__title' => 'color: {{VALUE}};',
455 ],
456 ]
457 );
458
459 $this->add_responsive_control(
460 'kng_title_spacing',
461 [
462 'label' => esc_html__('Title Bottom Spacing', 'king-addons'),
463 'type' => Controls_Manager::SLIDER,
464 'size_units' => ['px'],
465 'range' => [
466 'px' => ['min' => 0, 'max' => 60],
467 ],
468 'selectors' => [
469 '{{WRAPPER}} .king-addons-card-grid__title' => 'margin-bottom: {{SIZE}}{{UNIT}};',
470 ],
471 ]
472 );
473
474 $this->add_group_control(
475 Group_Control_Typography::get_type(),
476 [
477 'name' => 'kng_description_typography',
478 'selector' => '{{WRAPPER}} .king-addons-card-grid__description',
479 ]
480 );
481
482 $this->add_control(
483 'kng_description_color',
484 [
485 'label' => esc_html__('Description Color', 'king-addons'),
486 'type' => Controls_Manager::COLOR,
487 'selectors' => [
488 '{{WRAPPER}} .king-addons-card-grid__description' => 'color: {{VALUE}};',
489 ],
490 ]
491 );
492
493 $this->add_responsive_control(
494 'kng_description_spacing',
495 [
496 'label' => esc_html__('Description Bottom Spacing', 'king-addons'),
497 'type' => Controls_Manager::SLIDER,
498 'size_units' => ['px'],
499 'range' => [
500 'px' => ['min' => 0, 'max' => 80],
501 ],
502 'selectors' => [
503 '{{WRAPPER}} .king-addons-card-grid__description' => 'margin-bottom: {{SIZE}}{{UNIT}};',
504 ],
505 ]
506 );
507
508 $this->add_control(
509 'kng_text_alignment',
510 [
511 'label' => esc_html__('Text Alignment', 'king-addons'),
512 'type' => Controls_Manager::CHOOSE,
513 'options' => [
514 'left' => [
515 'title' => esc_html__('Left', 'king-addons'),
516 'icon' => 'eicon-text-align-left',
517 ],
518 'center' => [
519 'title' => esc_html__('Center', 'king-addons'),
520 'icon' => 'eicon-text-align-center',
521 ],
522 'right' => [
523 'title' => esc_html__('Right', 'king-addons'),
524 'icon' => 'eicon-text-align-right',
525 ],
526 ],
527 'toggle' => false,
528 'default' => 'left',
529 'prefix_class' => 'king-addons-card-grid--text-align-',
530 ]
531 );
532
533 $this->end_controls_section();
534 }
535
536 protected function register_style_button_controls(): void
537 {
538 $this->start_controls_section(
539 'kng_style_button_section',
540 [
541 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Button', 'king-addons'),
542 'tab' => Controls_Manager::TAB_STYLE,
543 ]
544 );
545
546 $this->add_control(
547 'kng_button_alignment',
548 [
549 'label' => esc_html__('Button Alignment', 'king-addons'),
550 'type' => Controls_Manager::CHOOSE,
551 'options' => [
552 'left' => [
553 'title' => esc_html__('Left', 'king-addons'),
554 'icon' => 'eicon-h-align-left',
555 ],
556 'center' => [
557 'title' => esc_html__('Center', 'king-addons'),
558 'icon' => 'eicon-h-align-center',
559 ],
560 'right' => [
561 'title' => esc_html__('Right', 'king-addons'),
562 'icon' => 'eicon-h-align-right',
563 ],
564 ],
565 'toggle' => false,
566 'default' => 'left',
567 'prefix_class' => 'king-addons-card-grid--button-align-',
568 ]
569 );
570
571 $this->add_control(
572 'kng_button_text_alignment',
573 [
574 'label' => esc_html__('Button Text Alignment', 'king-addons'),
575 'type' => Controls_Manager::CHOOSE,
576 'options' => [
577 'left' => [
578 'title' => esc_html__('Left', 'king-addons'),
579 'icon' => 'eicon-h-align-left',
580 ],
581 'center' => [
582 'title' => esc_html__('Center', 'king-addons'),
583 'icon' => 'eicon-h-align-center',
584 ],
585 'right' => [
586 'title' => esc_html__('Right', 'king-addons'),
587 'icon' => 'eicon-h-align-right',
588 ],
589 ],
590 'toggle' => false,
591 'default' => 'center',
592 'prefix_class' => 'king-addons-card-grid--button-text-align-',
593 ]
594 );
595
596 $this->add_group_control(
597 Group_Control_Typography::get_type(),
598 [
599 'name' => 'kng_button_typography',
600 'selector' => '{{WRAPPER}} .king-addons-card-grid__button',
601 ]
602 );
603
604 $this->start_controls_tabs('kng_button_tabs');
605
606 $this->start_controls_tab(
607 'kng_button_tab_normal',
608 [
609 'label' => esc_html__('Normal', 'king-addons'),
610 ]
611 );
612
613 $this->add_control(
614 'kng_button_text_color',
615 [
616 'label' => esc_html__('Text Color', 'king-addons'),
617 'type' => Controls_Manager::COLOR,
618 'selectors' => [
619 '{{WRAPPER}} .king-addons-card-grid__button' => 'color: {{VALUE}};',
620 ],
621 ]
622 );
623
624 $this->add_control(
625 'kng_button_background',
626 [
627 'label' => esc_html__('Background', 'king-addons'),
628 'type' => Controls_Manager::COLOR,
629 'selectors' => [
630 '{{WRAPPER}} .king-addons-card-grid__button' => 'background-color: {{VALUE}};',
631 ],
632 ]
633 );
634
635 $this->add_group_control(
636 Group_Control_Border::get_type(),
637 [
638 'name' => 'kng_button_border',
639 'selector' => '{{WRAPPER}} .king-addons-card-grid__button',
640 ]
641 );
642
643 $this->add_group_control(
644 Group_Control_Box_Shadow::get_type(),
645 [
646 'name' => 'kng_button_shadow',
647 'selector' => '{{WRAPPER}} .king-addons-card-grid__button',
648 ]
649 );
650
651 $this->end_controls_tab();
652
653 $this->start_controls_tab(
654 'kng_button_tab_hover',
655 [
656 'label' => esc_html__('Hover', 'king-addons'),
657 ]
658 );
659
660 $this->add_control(
661 'kng_button_text_color_hover',
662 [
663 'label' => esc_html__('Text Color', 'king-addons'),
664 'type' => Controls_Manager::COLOR,
665 'selectors' => [
666 '{{WRAPPER}} .king-addons-card-grid__button:hover' => 'color: {{VALUE}};',
667 ],
668 ]
669 );
670
671 $this->add_control(
672 'kng_button_background_hover',
673 [
674 'label' => esc_html__('Background', 'king-addons'),
675 'type' => Controls_Manager::COLOR,
676 'selectors' => [
677 '{{WRAPPER}} .king-addons-card-grid__button:hover' => 'background-color: {{VALUE}};',
678 ],
679 ]
680 );
681
682 $this->add_control(
683 'kng_button_border_color_hover',
684 [
685 'label' => esc_html__('Border Color', 'king-addons'),
686 'type' => Controls_Manager::COLOR,
687 'selectors' => [
688 '{{WRAPPER}} .king-addons-card-grid__button:hover' => 'border-color: {{VALUE}};',
689 ],
690 ]
691 );
692
693 $this->add_group_control(
694 Group_Control_Box_Shadow::get_type(),
695 [
696 'name' => 'kng_button_shadow_hover',
697 'selector' => '{{WRAPPER}} .king-addons-card-grid__button:hover',
698 ]
699 );
700
701 $this->end_controls_tab();
702 $this->end_controls_tabs();
703
704 $this->add_responsive_control(
705 'kng_button_padding',
706 [
707 'label' => esc_html__('Padding', 'king-addons'),
708 'type' => Controls_Manager::DIMENSIONS,
709 'size_units' => ['px', '%', 'em'],
710 'selectors' => [
711 '{{WRAPPER}} .king-addons-card-grid__button' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
712 ],
713 'separator' => 'before',
714 ]
715 );
716
717 $this->add_control(
718 'kng_button_radius',
719 [
720 'label' => esc_html__('Border Radius', 'king-addons'),
721 'type' => Controls_Manager::SLIDER,
722 'size_units' => ['px', '%'],
723 'range' => [
724 'px' => ['min' => 0, 'max' => 80],
725 '%' => ['min' => 0, 'max' => 100],
726 ],
727 'selectors' => [
728 '{{WRAPPER}} .king-addons-card-grid__button' => 'border-radius: {{SIZE}}{{UNIT}};',
729 ],
730 ]
731 );
732
733 $this->add_control(
734 'kng_button_shadow_disable',
735 [
736 'label' => esc_html__('Disable Box Shadow', 'king-addons'),
737 'type' => Controls_Manager::SWITCHER,
738 'label_on' => esc_html__('Yes', 'king-addons'),
739 'label_off' => esc_html__('No', 'king-addons'),
740 'return_value' => 'yes',
741 'default' => '',
742 'selectors' => [
743 '{{WRAPPER}} .king-addons-card-grid__button' => 'box-shadow: none !important;',
744 ],
745 ]
746 );
747
748 $this->add_control(
749 'kng_button_shadow_hover_disable',
750 [
751 'label' => esc_html__('Disable Box Shadow on Hover', 'king-addons'),
752 'type' => Controls_Manager::SWITCHER,
753 'label_on' => esc_html__('Yes', 'king-addons'),
754 'label_off' => esc_html__('No', 'king-addons'),
755 'return_value' => 'yes',
756 'default' => '',
757 'selectors' => [
758 '{{WRAPPER}} .king-addons-card-grid__button:hover' => 'box-shadow: none !important;',
759 ],
760 ]
761 );
762
763 $this->end_controls_section();
764 }
765
766 /**
767 * Render a single card item.
768 *
769 * @param array<string, mixed> $settings Widget settings.
770 * @param array<string, mixed> $card Card data.
771 *
772 * @return void
773 */
774 protected function render_card(array $settings, array $card): void
775 {
776 $card_classes = ['king-addons-card-grid__card'];
777 $card_classes = array_merge($card_classes, $this->get_additional_card_classes($settings, $card));
778
779 $card_link_enabled = ($card['kng_card_link_enable'] ?? '') === 'yes';
780 $card_link = $card['kng_card_link'] ?? [];
781 $card_link_attributes = $this->get_card_link_attributes($card_link_enabled, $card_link);
782 $card_attribute_string = $card_link_attributes ? ' ' . $card_link_attributes : '';
783
784 $image_html = $this->get_card_image($card);
785
786 $title = $card['kng_card_title'] ?? '';
787 $description = $card['kng_card_description'] ?? '';
788 $button_text = $card['kng_card_button_text'] ?? '';
789 $button_link = $card['kng_card_button_link'] ?? [];
790
791 ?>
792 <div class="king-addons-card-grid__item">
793 <article class="<?php echo esc_attr(implode(' ', array_filter($card_classes))); ?>"<?php echo $card_attribute_string; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
794 <?php if (!empty($image_html)) : ?>
795 <div class="king-addons-card-grid__media">
796 <?php echo $image_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
797 </div>
798 <?php endif; ?>
799
800 <div class="king-addons-card-grid__body">
801 <?php if (!empty($title)) : ?>
802 <h3 class="king-addons-card-grid__title"><?php echo esc_html($title); ?></h3>
803 <?php endif; ?>
804 <?php if (!empty($description)) : ?>
805 <div class="king-addons-card-grid__description"><?php echo esc_html($description); ?></div>
806 <?php endif; ?>
807 <?php if (!empty($button_text)) : ?>
808 <?php $this->render_button($button_text, $button_link); ?>
809 <?php endif; ?>
810 </div>
811 </article>
812 </div>
813 <?php
814 }
815
816 /**
817 * Build wrapper classes.
818 *
819 * @param array<string, mixed> $settings Widget settings.
820 *
821 * @return array<int, string>
822 */
823 protected function get_wrapper_classes(array $settings): array
824 {
825 $classes = ['king-addons-card-grid'];
826
827 $text_align = $settings['kng_text_alignment'] ?? 'left';
828 $classes[] = 'king-addons-card-grid--text-align-' . sanitize_html_class((string) $text_align);
829
830 $button_align = $settings['kng_button_alignment'] ?? 'left';
831 $classes[] = 'king-addons-card-grid--button-align-' . sanitize_html_class((string) $button_align);
832
833 $button_text_align = $settings['kng_button_text_alignment'] ?? 'center';
834 $classes[] = 'king-addons-card-grid--button-text-align-' . sanitize_html_class((string) $button_text_align);
835
836 $classes = array_merge($classes, $this->get_additional_wrapper_classes($settings));
837
838 return array_filter($classes);
839 }
840
841 /**
842 * Build wrapper inline styles.
843 *
844 * @param array<string, mixed> $settings Widget settings.
845 *
846 * @return string
847 */
848 protected function get_wrapper_style(array $settings): string
849 {
850 $style_parts = [];
851
852 if (isset($settings['kng_grid_gap']['size'])) {
853 $style_parts[] = '--kng-card-grid-gap:' . (float) $settings['kng_grid_gap']['size'] . ($settings['kng_grid_gap']['unit'] ?? 'px') . ';';
854 }
855
856 if (!empty($style_parts)) {
857 return ' style="' . esc_attr(implode(' ', $style_parts)) . '"';
858 }
859
860 return '';
861 }
862
863 /**
864 * Get card image HTML.
865 *
866 * @param array<string, mixed> $card Card data.
867 *
868 * @return string
869 */
870 protected function get_card_image(array $card): string
871 {
872 if (empty($card['kng_card_image']['id']) && empty($card['kng_card_image']['url'])) {
873 return '';
874 }
875
876 $image_html = Group_Control_Image_Size::get_attachment_image_html($card, 'kng_card_image');
877
878 if (!empty($image_html)) {
879 return $image_html;
880 }
881
882 return '<img src="' . esc_url(Utils::get_placeholder_image_src()) . '" alt="' . esc_attr($card['kng_card_title'] ?? '') . '"/>';
883 }
884
885 /**
886 * Render button element.
887 *
888 * @param string $text Button text.
889 * @param array<string, mixed> $link Link data.
890 *
891 * @return void
892 */
893 protected function render_button(string $text, array $link): void
894 {
895 $url = $link['url'] ?? '';
896 $rel = [];
897
898 if (!empty($link['nofollow'])) {
899 $rel[] = 'nofollow';
900 }
901
902 if (!empty($link['is_external'])) {
903 $rel[] = 'noopener';
904 $rel[] = 'noreferrer';
905 }
906
907 $attributes = '';
908
909 if (!empty($url)) {
910 $attributes .= ' href="' . esc_url($url) . '"';
911 }
912
913 if (!empty($link['is_external'])) {
914 $attributes .= ' target="_blank"';
915 }
916
917 if (!empty($rel)) {
918 $attributes .= ' rel="' . esc_attr(implode(' ', array_unique($rel))) . '"';
919 }
920
921 ?>
922 <a class="king-addons-card-grid__button"<?php echo $attributes; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
923 <?php echo esc_html($text); ?>
924 </a>
925 <?php
926 }
927
928 /**
929 * Build card-level link data attributes.
930 *
931 * @param bool $enabled Whether link is enabled.
932 * @param array<string, mixed> $link Link data.
933 *
934 * @return string
935 */
936 protected function get_card_link_attributes(bool $enabled, array $link): string
937 {
938 if (!$enabled || empty($link['url'])) {
939 return '';
940 }
941
942 $attributes = [
943 'data-card-link' => esc_url_raw($link['url']),
944 ];
945
946 if (!empty($link['is_external'])) {
947 $attributes['data-card-link-target'] = '_blank';
948 }
949
950 if (!empty($link['nofollow'])) {
951 $attributes['data-card-link-rel'] = 'nofollow';
952 }
953
954 $output = [];
955 foreach ($attributes as $key => $value) {
956 $output[] = esc_attr($key) . '="' . esc_attr((string) $value) . '"';
957 }
958
959 return implode(' ', $output);
960 }
961
962 /**
963 * Placeholder for pro wrapper classes.
964 *
965 * @param array<string, mixed> $settings Widget settings.
966 *
967 * @return array<int, string>
968 */
969 protected function get_additional_wrapper_classes(array $settings): array
970 {
971 unset($settings);
972 return [];
973 }
974
975 /**
976 * Placeholder for pro card classes.
977 *
978 * @param array<string, mixed> $settings Widget settings.
979 * @param array<string, mixed> $card Card data.
980 *
981 * @return array<int, string>
982 */
983 protected function get_additional_card_classes(array $settings, array $card): array
984 {
985 unset($settings, $card);
986 return [];
987 }
988
989 /**
990 * Render premium upgrade notice.
991 *
992 * @return void
993 */
994 public function register_pro_notice_controls(): void
995 {
996 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
997 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'quick-card-grid', [
998 'Advanced hover animations for cards',
999 'Extended styling controls and presets',
1000 'Additional layout polish for cards and buttons',
1001 ]);
1002 }
1003 }
1004 }
1005
1006
1007
1008
1009
1010
1011
1012