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

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

1,600 lines 55.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Rotating Image Tiles 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_Typography;
14 use Elementor\Repeater;
15 use Elementor\Utils;
16 use Elementor\Widget_Base;
17
18 if (!defined('ABSPATH')) {
19 exit;
20 }
21
22 /**
23 * Renders the Rotating Image Tiles widget.
24 */
25 class Rotating_Image_Tiles extends Widget_Base
26 {
27 private const FREE_MAX_IMAGES = 2;
28 private const FREE_MAX_TILES = 8;
29 private const FREE_MAX_COLUMNS = 3;
30 private const DEFAULT_ROTATION_INTERVAL = 2500;
31 private const DEFAULT_TRANSITION_DURATION = 800;
32
33 /**
34 * Widget slug.
35 *
36 * @return string
37 */
38 public function get_name(): string
39 {
40 return 'king-addons-rotating-image-tiles';
41 }
42
43 /**
44 * Widget title.
45 *
46 * @return string
47 */
48 public function get_title(): string
49 {
50 return esc_html__('Rotating Image Tiles', 'king-addons');
51 }
52
53 /**
54 * Widget icon.
55 *
56 * @return string
57 */
58 public function get_icon(): string
59 {
60 return 'king-addons-icon king-addons-rotating-image-tiles';
61 }
62
63 /**
64 * Script dependencies.
65 *
66 * @return array<int, string>
67 */
68 public function get_script_depends(): array
69 {
70 return [
71 KING_ADDONS_ASSETS_UNIQUE_KEY . '-rotating-image-tiles-script',
72 ];
73 }
74
75 /**
76 * Style dependencies.
77 *
78 * @return array<int, string>
79 */
80 public function get_style_depends(): array
81 {
82 return [
83 KING_ADDONS_ASSETS_UNIQUE_KEY . '-rotating-image-tiles-style',
84 ];
85 }
86
87 /**
88 * Widget categories.
89 *
90 * @return array<int, string>
91 */
92 public function get_categories(): array
93 {
94 return ['king-addons'];
95 }
96
97 /**
98 * Widget keywords.
99 *
100 * @return array<int, string>
101 */
102 public function get_keywords(): array
103 {
104 return ['image', 'tiles', 'rotation', 'gallery', 'grid'];
105 }
106
107 public function get_custom_help_url()
108 {
109 return 'mailto:bug@kingaddons.com?subject=Bug Report - King Addons&body=Please describe the issue';
110 }
111
112 /**
113 * Register Elementor controls.
114 *
115 * @return void
116 */
117 public function register_controls(): void
118 {
119 $this->register_content_images_controls(false);
120 $this->register_layout_controls(false);
121 $this->register_tiles_controls(false);
122 $this->register_animation_controls(false);
123 $this->register_interaction_controls(false);
124 $this->register_style_holder_controls();
125 $this->register_style_tiles_controls(false);
126 $this->register_style_circle_controls(false);
127 $this->register_style_images_controls(false);
128 $this->register_style_overlay_controls(false);
129 $this->register_style_caption_controls(false);
130 $this->register_pro_notice_controls();
131 }
132
133 /**
134 * Render widget output.
135 *
136 * @return void
137 */
138 public function render(): void
139 {
140 $settings = $this->get_settings_for_display();
141 $this->render_output($settings, false);
142 }
143
144 /**
145 * Render markup shared between free and pro.
146 *
147 * @param array<string, mixed> $settings Settings.
148 * @param bool $is_pro Whether pro version is active.
149 *
150 * @return void
151 */
152 public function render_output(array $settings, bool $is_pro): void
153 {
154 $images = $this->prepare_images($settings, $is_pro);
155 $tiles = $this->prepare_tiles($settings, $is_pro, count($images));
156
157 if (empty($images) || empty($tiles)) {
158 return;
159 }
160
161 $layout_columns = $this->get_int_setting($settings['kng_tiles_columns'] ?? ($is_pro ? 4 : self::FREE_MAX_COLUMNS), 1, $is_pro ? 8 : self::FREE_MAX_COLUMNS);
162 $layout_gap = $this->get_int_setting($settings['kng_tiles_gap']['size'] ?? 12, 0, 120);
163
164 $rotation_interval = $this->get_int_setting(
165 $settings['kng_rotation_interval']['size'] ?? self::DEFAULT_ROTATION_INTERVAL,
166 100,
167 20000
168 );
169 $transition_duration = $this->get_int_setting(
170 $settings['kng_transition_duration']['size'] ?? self::DEFAULT_TRANSITION_DURATION,
171 100,
172 4000
173 );
174
175 $rotation_behavior = $is_pro ? ($settings['kng_rotation_behavior'] ?? 'autoplay') : 'autoplay';
176 if (!$is_pro && ($settings['kng_enable_rotation'] ?? 'yes') !== 'yes') {
177 $rotation_behavior = 'none';
178 }
179
180 $data_settings = [
181 'isPro' => $is_pro,
182 'layout' => [
183 'columns' => $layout_columns,
184 'gap' => $layout_gap,
185 ],
186 'animation' => [
187 'mode' => $is_pro ? ($settings['kng_rotation_mode'] ?? 'sequential') : 'sequential',
188 'behavior' => $rotation_behavior,
189 'interval' => $rotation_interval,
190 'transitionDuration' => $transition_duration,
191 'easing' => $is_pro ? ($settings['kng_easing'] ?? 'ease-in-out') : 'ease-in-out',
192 'loop' => $is_pro ? ($settings['kng_loop'] ?? 'yes') : 'yes',
193 'pauseOnHover' => $is_pro ? ($settings['kng_pause_on_hover'] ?? 'no') : 'no',
194 'disableOnMobile' => $is_pro ? ($settings['kng_disable_animation_on_mobile'] ?? 'no') : 'no',
195 ],
196 'interaction' => [
197 'clickAction' => $is_pro ? ($settings['kng_click_action'] ?? 'lightbox') : 'lightbox',
198 'showCaptions' => $settings['kng_show_captions'] ?? 'no',
199 'captionSource' => $is_pro ? ($settings['kng_caption_source'] ?? 'title') : 'title',
200 ],
201 'visual' => [
202 'imageFit' => $settings['kng_image_fit'] ?? 'cover',
203 'imageScaleHover' => $is_pro ? ($settings['kng_image_scale_hover']['size'] ?? 1.05) : 1.05,
204 'imageOpacity' => $settings['kng_image_opacity']['size'] ?? 1,
205 'imageOpacityHover' => $is_pro ? ($settings['kng_image_opacity_hover']['size'] ?? 1) : ($settings['kng_image_opacity']['size'] ?? 1),
206 ],
207 'circle' => [
208 'useGlobal' => $is_pro ? ($settings['kng_use_global_radius'] ?? 'no') : 'yes',
209 'radius' => $is_pro ? ($settings['kng_circle_global_radius']['size'] ?? 40) : 40,
210 ],
211 'overlay' => [
212 'color' => $settings['kng_overlay_color'] ?? '',
213 'opacity' => $settings['kng_overlay_opacity']['size'] ?? 0,
214 'blendMode' => $settings['kng_overlay_blend_mode'] ?? 'normal',
215 ],
216 'hover' => [
217 'tileEffect' => $is_pro ? ($settings['kng_tile_hover_effect'] ?? 'none') : 'none',
218 ],
219 'tiles' => $tiles,
220 'images' => $images,
221 ];
222
223 $holder_style = $this->build_holder_style($settings);
224 $grid_style = sprintf('--rit-columns:%d;--rit-gap:%dpx;', $layout_columns, $layout_gap);
225 $grid_style .= sprintf('--rit-transition-duration:%dms;--rit-easing:%s;', $transition_duration, esc_attr($data_settings['animation']['easing']));
226 $grid_style .= sprintf('--rit-image-fit:%s;', esc_attr($data_settings['visual']['imageFit']));
227
228 ?>
229 <div class="king-addons-rotating-image-tiles" data-settings="<?php echo esc_attr(wp_json_encode($data_settings)); ?>">
230 <div class="king-addons-rotating-image-tiles__holder"<?php echo $holder_style; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
231 <div class="king-addons-rotating-image-tiles__grid" style="<?php echo esc_attr($grid_style); ?>">
232 <?php foreach ($tiles as $index => $tile) : ?>
233 <?php
234 $current_image_index = $tile['initialIndex'] ?? 0;
235 $current_image = $images[$current_image_index] ?? $images[0];
236 $mask_radius = $this->get_float_setting($tile['radius'] ?? 40, 5, 100);
237 $mask_center_x = $this->get_float_setting($tile['centerX'] ?? 50, 0, 100);
238 $mask_center_y = $this->get_float_setting($tile['centerY'] ?? 50, 0, 100);
239 $mask_style = sprintf(
240 'clip-path:circle(%s%% at %s%% %s%%);',
241 esc_attr($mask_radius),
242 esc_attr($mask_center_x),
243 esc_attr($mask_center_y)
244 );
245 ?>
246 <div
247 class="king-addons-rotating-image-tiles__tile"
248 data-tile-index="<?php echo esc_attr($index); ?>"
249 data-initial-index="<?php echo esc_attr($current_image_index); ?>"
250 data-delay="<?php echo esc_attr((int) ($tile['delay'] ?? 0)); ?>"
251 data-center-x="<?php echo esc_attr($mask_center_x); ?>"
252 data-center-y="<?php echo esc_attr($mask_center_y); ?>"
253 data-radius="<?php echo esc_attr($mask_radius); ?>"
254 data-hover-scale="<?php echo esc_attr($tile['hoverScale'] ?? 1.05); ?>"
255 >
256 <div class="king-addons-rotating-image-tiles__tile-inner">
257 <div class="king-addons-rotating-image-tiles__image-wrapper">
258 <div class="king-addons-rotating-image-tiles__image-layer is-current" style="<?php echo esc_attr($mask_style); ?>">
259 <img src="<?php echo esc_url($current_image['url']); ?>" alt="<?php echo esc_attr($current_image['alt']); ?>" />
260 <span class="king-addons-rotating-image-tiles__overlay"></span>
261 </div>
262 <div class="king-addons-rotating-image-tiles__image-layer is-next" style="<?php echo esc_attr($mask_style); ?>">
263 <img src="<?php echo esc_url($current_image['url']); ?>" alt="<?php echo esc_attr($current_image['alt']); ?>" />
264 <span class="king-addons-rotating-image-tiles__overlay"></span>
265 </div>
266 </div>
267 <?php if (($settings['kng_show_captions'] ?? 'no') === 'yes') : ?>
268 <div class="king-addons-rotating-image-tiles__caption">
269 <?php if (!empty($current_image['title'])) : ?>
270 <div class="king-addons-rotating-image-tiles__caption-title">
271 <?php echo esc_html($current_image['title']); ?>
272 </div>
273 <?php endif; ?>
274 <?php if (($settings['kng_caption_source'] ?? 'title') === 'title_description' && !empty($current_image['description'])) : ?>
275 <div class="king-addons-rotating-image-tiles__caption-description">
276 <?php echo esc_html($current_image['description']); ?>
277 </div>
278 <?php endif; ?>
279 </div>
280 <?php endif; ?>
281 </div>
282 </div>
283 <?php endforeach; ?>
284 </div>
285 </div>
286 </div>
287 <?php
288 }
289
290 /**
291 * Register images repeater controls.
292 *
293 * @param bool $is_pro Whether pro controls should be displayed.
294 *
295 * @return void
296 */
297 public function register_content_images_controls(bool $is_pro): void
298 {
299 $this->start_controls_section(
300 'kng_images_section',
301 [
302 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Images', 'king-addons'),
303 'tab' => Controls_Manager::TAB_CONTENT,
304 ]
305 );
306
307 $repeater = new Repeater();
308
309 $repeater->add_control(
310 'image',
311 [
312 'label' => esc_html__('Image', 'king-addons'),
313 'type' => Controls_Manager::MEDIA,
314 'default' => [
315 'url' => Utils::get_placeholder_image_src(),
316 ],
317 ]
318 );
319
320 $repeater->add_control(
321 'title',
322 [
323 'label' => esc_html__('Title', 'king-addons'),
324 'type' => Controls_Manager::TEXT,
325 'label_block' => true,
326 ]
327 );
328
329 $repeater->add_control(
330 'description',
331 [
332 'label' => esc_html__('Description', 'king-addons'),
333 'type' => Controls_Manager::TEXTAREA,
334 'rows' => 3,
335 ]
336 );
337
338 if ($is_pro) {
339 $repeater->add_control(
340 'link_type',
341 [
342 'label' => esc_html__('Link Type', 'king-addons'),
343 'type' => Controls_Manager::SELECT,
344 'default' => 'none',
345 'options' => [
346 'none' => esc_html__('None', 'king-addons'),
347 'custom' => esc_html__('Custom URL', 'king-addons'),
348 ],
349 ]
350 );
351
352 $repeater->add_control(
353 'link_url',
354 [
355 'label' => esc_html__('Link', 'king-addons'),
356 'type' => Controls_Manager::URL,
357 'condition' => [
358 'link_type' => 'custom',
359 ],
360 ]
361 );
362 }
363
364 $this->add_control(
365 'kng_images',
366 [
367 'label' => esc_html__('Images', 'king-addons'),
368 'type' => Controls_Manager::REPEATER,
369 'fields' => $repeater->get_controls(),
370 'title_field' => '{{ title || "' . esc_html__('Image', 'king-addons') . '" }}',
371 'default' => [
372 [
373 'image' => ['url' => Utils::get_placeholder_image_src()],
374 'title' => esc_html__('Primary', 'king-addons'),
375 ],
376 [
377 'image' => ['url' => Utils::get_placeholder_image_src()],
378 'title' => esc_html__('Secondary', 'king-addons'),
379 ],
380 ],
381 'description' => $is_pro
382 ? esc_html__('Add up to four images for rotation.', 'king-addons')
383 : esc_html__('Free version uses the first two images. Upgrade to Pro to unlock four images, captions, and links.', 'king-addons'),
384 ]
385 );
386
387 $this->end_controls_section();
388 }
389
390 /**
391 * Register layout controls.
392 *
393 * @param bool $is_pro Whether pro controls should be displayed.
394 *
395 * @return void
396 */
397 public function register_layout_controls(bool $is_pro): void
398 {
399 $this->start_controls_section(
400 'kng_layout_section',
401 [
402 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
403 'tab' => Controls_Manager::TAB_CONTENT,
404 ]
405 );
406
407 $this->add_responsive_control(
408 'kng_holder_width',
409 [
410 'label' => esc_html__('Max Width', 'king-addons'),
411 'type' => Controls_Manager::SLIDER,
412 'size_units' => ['px', '%', 'vw'],
413 'range' => [
414 'px' => [
415 'min' => 50,
416 'max' => 2000,
417 ],
418 '%' => [
419 'min' => 10,
420 'max' => 100,
421 ],
422 ],
423 'default' => [
424 'size' => 100,
425 'unit' => '%',
426 ],
427 'selectors' => [
428 '{{WRAPPER}} .king-addons-rotating-image-tiles__holder' => 'max-width: {{SIZE}}{{UNIT}};',
429 ],
430 ]
431 );
432
433 $this->add_responsive_control(
434 'kng_holder_height',
435 [
436 'label' => esc_html__('Height', 'king-addons'),
437 'type' => Controls_Manager::SLIDER,
438 'size_units' => ['px', 'vh'],
439 'range' => [
440 'px' => [
441 'min' => 120,
442 'max' => 1200,
443 ],
444 'vh' => [
445 'min' => 10,
446 'max' => 100,
447 ],
448 ],
449 'selectors' => [
450 '{{WRAPPER}} .king-addons-rotating-image-tiles__holder' => 'height: {{SIZE}}{{UNIT}};',
451 ],
452 ]
453 );
454
455 $this->add_control(
456 'kng_tiles_count',
457 [
458 'label' => esc_html__('Tiles Count', 'king-addons'),
459 'type' => Controls_Manager::NUMBER,
460 'min' => 1,
461 'max' => $is_pro ? 16 : self::FREE_MAX_TILES,
462 'step' => 1,
463 'default' => $is_pro ? 9 : 6,
464 'description' => $is_pro
465 ? esc_html__('Define how many tiles should render (up to 16).', 'king-addons')
466 : esc_html__('Free version renders up to 8 tiles.', 'king-addons'),
467 ]
468 );
469
470 $this->add_control(
471 'kng_tiles_columns',
472 [
473 'label' => esc_html__('Columns', 'king-addons'),
474 'type' => Controls_Manager::NUMBER,
475 'min' => 1,
476 'max' => $is_pro ? 8 : self::FREE_MAX_COLUMNS,
477 'step' => 1,
478 'default' => $is_pro ? 4 : 3,
479 'description' => $is_pro
480 ? esc_html__('Columns across the grid (up to 8).', 'king-addons')
481 : esc_html__('Free version supports up to 3 columns.', 'king-addons'),
482 ]
483 );
484
485 $this->add_responsive_control(
486 'kng_tiles_gap',
487 [
488 'label' => esc_html__('Tiles Gap', 'king-addons'),
489 'type' => Controls_Manager::SLIDER,
490 'size_units' => ['px', 'em'],
491 'range' => [
492 'px' => [
493 'min' => 0,
494 'max' => 64,
495 ],
496 'em' => [
497 'min' => 0,
498 'max' => 4,
499 ],
500 ],
501 'default' => [
502 'size' => 12,
503 'unit' => 'px',
504 ],
505 'selectors' => [
506 '{{WRAPPER}} .king-addons-rotating-image-tiles__grid' => 'gap: {{SIZE}}{{UNIT}};',
507 ],
508 ]
509 );
510
511 $this->end_controls_section();
512 }
513
514 /**
515 * Register tiles controls.
516 *
517 * @param bool $is_pro Whether pro controls should be displayed.
518 *
519 * @return void
520 */
521 public function register_tiles_controls(bool $is_pro): void
522 {
523 $this->start_controls_section(
524 'kng_tiles_section',
525 [
526 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Tiles', 'king-addons'),
527 'tab' => Controls_Manager::TAB_CONTENT,
528 ]
529 );
530
531 if (!$is_pro) {
532 $this->add_control(
533 'kng_tiles_locked_notice',
534 [
535 'type' => Controls_Manager::RAW_HTML,
536 'raw' => esc_html__('Per-tile radius, center, delays, and hover effects are available in Pro. Free version uses a uniform 40% circle centered in each tile.', 'king-addons'),
537 'content_classes' => 'king-addons-pro-notice',
538 ]
539 );
540 $this->end_controls_section();
541 return;
542 }
543
544 $repeater = new Repeater();
545
546 $repeater->add_control(
547 'admin_label',
548 [
549 'label' => esc_html__('Admin Label', 'king-addons'),
550 'type' => Controls_Manager::TEXT,
551 'label_block' => true,
552 'description' => esc_html__('Internal name for the tile.', 'king-addons'),
553 ]
554 );
555
556 $repeater->add_control(
557 'initial_image_index',
558 [
559 'label' => esc_html__('Initial Image', 'king-addons'),
560 'type' => Controls_Manager::SELECT,
561 'default' => '0',
562 'options' => [
563 '0' => esc_html__('Image 1', 'king-addons'),
564 '1' => esc_html__('Image 2', 'king-addons'),
565 '2' => esc_html__('Image 3', 'king-addons'),
566 '3' => esc_html__('Image 4', 'king-addons'),
567 ],
568 ]
569 );
570
571 $repeater->add_control(
572 'tile_circle_center_x',
573 [
574 'label' => esc_html__('Circle Center X (%)', 'king-addons'),
575 'type' => Controls_Manager::SLIDER,
576 'size_units' => ['%'],
577 'range' => [
578 '%' => [
579 'min' => 0,
580 'max' => 100,
581 ],
582 ],
583 'default' => [
584 'size' => 50,
585 'unit' => '%',
586 ],
587 ]
588 );
589
590 $repeater->add_control(
591 'tile_circle_center_y',
592 [
593 'label' => esc_html__('Circle Center Y (%)', 'king-addons'),
594 'type' => Controls_Manager::SLIDER,
595 'size_units' => ['%'],
596 'range' => [
597 '%' => [
598 'min' => 0,
599 'max' => 100,
600 ],
601 ],
602 'default' => [
603 'size' => 50,
604 'unit' => '%',
605 ],
606 ]
607 );
608
609 $repeater->add_control(
610 'tile_circle_radius',
611 [
612 'label' => esc_html__('Circle Radius (%)', 'king-addons'),
613 'type' => Controls_Manager::SLIDER,
614 'size_units' => ['%'],
615 'range' => [
616 '%' => [
617 'min' => 5,
618 'max' => 100,
619 ],
620 ],
621 'default' => [
622 'size' => 40,
623 'unit' => '%',
624 ],
625 ]
626 );
627
628 $repeater->add_control(
629 'delay_offset',
630 [
631 'label' => esc_html__('Delay Offset (ms)', 'king-addons'),
632 'type' => Controls_Manager::SLIDER,
633 'size_units' => ['ms'],
634 'range' => [
635 'ms' => [
636 'min' => 0,
637 'max' => 5000,
638 ],
639 ],
640 'default' => [
641 'size' => 0,
642 'unit' => 'ms',
643 ],
644 ]
645 );
646
647 $repeater->add_control(
648 'hover_scale',
649 [
650 'label' => esc_html__('Hover Scale', 'king-addons'),
651 'type' => Controls_Manager::SLIDER,
652 'size_units' => ['px'],
653 'range' => [
654 'px' => [
655 'min' => 1,
656 'max' => 1.3,
657 'step' => 0.01,
658 ],
659 ],
660 'default' => [
661 'size' => 1.05,
662 'unit' => 'px',
663 ],
664 ]
665 );
666
667 $this->add_control(
668 'kng_tiles',
669 [
670 'label' => esc_html__('Tiles', 'king-addons'),
671 'type' => Controls_Manager::REPEATER,
672 'fields' => $repeater->get_controls(),
673 'title_field' => '{{ admin_label || "' . esc_html__('Tile', 'king-addons') . '" }}',
674 'description' => esc_html__('Add one entry per tile. If fewer items are set than the count, defaults are used.', 'king-addons'),
675 ]
676 );
677
678 $this->end_controls_section();
679 }
680
681 /**
682 * Register animation controls.
683 *
684 * @param bool $is_pro Whether pro controls should be displayed.
685 *
686 * @return void
687 */
688 public function register_animation_controls(bool $is_pro): void
689 {
690 $this->start_controls_section(
691 'kng_animation_section',
692 [
693 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Animation', 'king-addons'),
694 'tab' => Controls_Manager::TAB_CONTENT,
695 ]
696 );
697
698 if ($is_pro) {
699 $this->add_control(
700 'kng_rotation_mode',
701 [
702 'label' => esc_html__('Rotation Mode', 'king-addons'),
703 'type' => Controls_Manager::SELECT,
704 'default' => 'sequential',
705 'options' => [
706 'sequential' => esc_html__('Sequential', 'king-addons'),
707 'reverse' => esc_html__('Reverse', 'king-addons'),
708 'alternate' => esc_html__('Alternate', 'king-addons'),
709 'random' => esc_html__('Random', 'king-addons'),
710 ],
711 ]
712 );
713
714 $this->add_control(
715 'kng_rotation_behavior',
716 [
717 'label' => esc_html__('Rotation Trigger', 'king-addons'),
718 'type' => Controls_Manager::SELECT,
719 'default' => 'autoplay',
720 'options' => [
721 'autoplay' => esc_html__('Autoplay', 'king-addons'),
722 'on_hover' => esc_html__('On Hover', 'king-addons'),
723 'on_click' => esc_html__('On Click', 'king-addons'),
724 ],
725 ]
726 );
727
728 $this->add_control(
729 'kng_loop',
730 [
731 'label' => esc_html__('Loop', 'king-addons'),
732 'type' => Controls_Manager::SWITCHER,
733 'return_value' => 'yes',
734 'default' => 'yes',
735 ]
736 );
737
738 $this->add_control(
739 'kng_pause_on_hover',
740 [
741 'label' => esc_html__('Pause on Hover', 'king-addons'),
742 'type' => Controls_Manager::SWITCHER,
743 'return_value' => 'yes',
744 'default' => 'no',
745 'condition' => [
746 'kng_rotation_behavior' => 'autoplay',
747 ],
748 ]
749 );
750 } else {
751 $this->add_control(
752 'kng_enable_rotation',
753 [
754 'label' => esc_html__('Enable Rotation', 'king-addons'),
755 'type' => Controls_Manager::SWITCHER,
756 'return_value' => 'yes',
757 'default' => 'yes',
758 'description' => esc_html__('Free version uses autoplay with fixed easing and timing.', 'king-addons'),
759 ]
760 );
761 }
762
763 $this->add_control(
764 'kng_rotation_interval',
765 [
766 'label' => $is_pro ? esc_html__('Rotation Interval (ms)', 'king-addons') : esc_html__('Rotation Interval (fixed)', 'king-addons'),
767 'type' => Controls_Manager::SLIDER,
768 'size_units' => ['ms'],
769 'range' => [
770 'ms' => [
771 'min' => 500,
772 'max' => 10000,
773 ],
774 ],
775 'default' => [
776 'size' => self::DEFAULT_ROTATION_INTERVAL,
777 'unit' => 'ms',
778 ],
779 'description' => $is_pro
780 ? esc_html__('Interval between rotations per tile.', 'king-addons')
781 : esc_html__('Fixed for free version; adjust with Pro.', 'king-addons'),
782 'render_type' => 'none',
783 ]
784 );
785
786 $this->add_control(
787 'kng_transition_duration',
788 [
789 'label' => $is_pro ? esc_html__('Transition Duration (ms)', 'king-addons') : esc_html__('Transition Duration (fixed)', 'king-addons'),
790 'type' => Controls_Manager::SLIDER,
791 'size_units' => ['ms'],
792 'range' => [
793 'ms' => [
794 'min' => 100,
795 'max' => 3000,
796 ],
797 ],
798 'default' => [
799 'size' => self::DEFAULT_TRANSITION_DURATION,
800 'unit' => 'ms',
801 ],
802 'description' => $is_pro
803 ? esc_html__('Fade/transform duration.', 'king-addons')
804 : esc_html__('Fixed for free version; adjust with Pro.', 'king-addons'),
805 'render_type' => 'none',
806 ]
807 );
808
809 if ($is_pro) {
810 $this->add_control(
811 'kng_easing',
812 [
813 'label' => esc_html__('Easing', 'king-addons'),
814 'type' => Controls_Manager::SELECT,
815 'default' => 'ease',
816 'options' => [
817 'ease' => esc_html__('Ease', 'king-addons'),
818 'linear' => esc_html__('Linear', 'king-addons'),
819 'ease-in' => esc_html__('Ease In', 'king-addons'),
820 'ease-out' => esc_html__('Ease Out', 'king-addons'),
821 'ease-in-out' => esc_html__('Ease In Out', 'king-addons'),
822 ],
823 ]
824 );
825
826 $this->add_control(
827 'kng_disable_animation_on_mobile',
828 [
829 'label' => esc_html__('Disable Animation on Mobile', 'king-addons'),
830 'type' => Controls_Manager::SWITCHER,
831 'return_value' => 'yes',
832 'default' => 'no',
833 ]
834 );
835 }
836
837 $this->end_controls_section();
838 }
839
840 /**
841 * Register interaction controls.
842 *
843 * @param bool $is_pro Whether pro controls should be displayed.
844 *
845 * @return void
846 */
847 public function register_interaction_controls(bool $is_pro): void
848 {
849 $this->start_controls_section(
850 'kng_interaction_section',
851 [
852 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Interaction', 'king-addons'),
853 'tab' => Controls_Manager::TAB_CONTENT,
854 ]
855 );
856
857 if ($is_pro) {
858 $this->add_control(
859 'kng_click_action',
860 [
861 'label' => esc_html__('Click Action', 'king-addons'),
862 'type' => Controls_Manager::SELECT,
863 'default' => 'lightbox',
864 'options' => [
865 'none' => esc_html__('None', 'king-addons'),
866 'lightbox' => esc_html__('Open Image in Lightbox', 'king-addons'),
867 'open_link' => esc_html__('Open Link', 'king-addons'),
868 ],
869 ]
870 );
871 } else {
872 $this->add_control(
873 'kng_click_action_locked',
874 [
875 'type' => Controls_Manager::RAW_HTML,
876 'raw' => esc_html__('Clicks open the image in a simple lightbox. Upgrade to Pro to unlock custom links and click behaviors.', 'king-addons'),
877 'content_classes' => 'king-addons-pro-notice',
878 ]
879 );
880 }
881
882 $this->add_control(
883 'kng_show_captions',
884 [
885 'label' => esc_html__('Show Captions', 'king-addons'),
886 'type' => Controls_Manager::SWITCHER,
887 'return_value' => 'yes',
888 'default' => 'no',
889 ]
890 );
891
892 $this->add_control(
893 'kng_caption_source',
894 [
895 'label' => sprintf(esc_html__('Caption Source %s', 'king-addons'), $is_pro ? '' : '<i class="eicon-pro-icon"></i>'),
896 'type' => Controls_Manager::SELECT,
897 'default' => 'title',
898 'options' => [
899 'title' => esc_html__('Title Only', 'king-addons'),
900 'title_description' => esc_html__('Title and Description', 'king-addons'),
901 ],
902 'condition' => [
903 'kng_show_captions' => 'yes',
904 ],
905 'description' => $is_pro ? '' : esc_html__('Pro unlocks description captions.', 'king-addons'),
906 ]
907 );
908
909 $this->end_controls_section();
910 }
911
912 /**
913 * Register holder style controls.
914 *
915 * @return void
916 */
917 public function register_style_holder_controls(): void
918 {
919 $this->start_controls_section(
920 'kng_style_holder_section',
921 [
922 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Holder', 'king-addons'),
923 'tab' => Controls_Manager::TAB_STYLE,
924 ]
925 );
926
927 $this->add_responsive_control(
928 'kng_holder_alignment',
929 [
930 'label' => esc_html__('Alignment', 'king-addons'),
931 'type' => Controls_Manager::CHOOSE,
932 'options' => [
933 'flex-start' => [
934 'title' => esc_html__('Left', 'king-addons'),
935 'icon' => 'eicon-h-align-left',
936 ],
937 'center' => [
938 'title' => esc_html__('Center', 'king-addons'),
939 'icon' => 'eicon-h-align-center',
940 ],
941 'flex-end' => [
942 'title' => esc_html__('Right', 'king-addons'),
943 'icon' => 'eicon-h-align-right',
944 ],
945 ],
946 'default' => 'center',
947 'selectors' => [
948 '{{WRAPPER}} .king-addons-rotating-image-tiles' => 'display:flex;justify-content: {{VALUE}};',
949 ],
950 ]
951 );
952
953 $this->add_control(
954 'kng_holder_background_color',
955 [
956 'label' => esc_html__('Background Color', 'king-addons'),
957 'type' => Controls_Manager::COLOR,
958 'selectors' => [
959 '{{WRAPPER}} .king-addons-rotating-image-tiles__holder' => 'background-color: {{VALUE}};',
960 ],
961 ]
962 );
963
964 $this->add_group_control(
965 Group_Control_Border::get_type(),
966 [
967 'name' => 'kng_holder_border',
968 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__holder',
969 ]
970 );
971
972 $this->add_responsive_control(
973 'kng_holder_border_radius',
974 [
975 'label' => esc_html__('Border Radius', 'king-addons'),
976 'type' => Controls_Manager::SLIDER,
977 'size_units' => ['px', '%'],
978 'range' => [
979 'px' => [
980 'min' => 0,
981 'max' => 100,
982 ],
983 '%' => [
984 'min' => 0,
985 'max' => 100,
986 ],
987 ],
988 'selectors' => [
989 '{{WRAPPER}} .king-addons-rotating-image-tiles__holder' => 'border-radius: {{SIZE}}{{UNIT}};',
990 ],
991 ]
992 );
993
994 $this->add_group_control(
995 Group_Control_Box_Shadow::get_type(),
996 [
997 'name' => 'kng_holder_box_shadow',
998 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__holder',
999 ]
1000 );
1001
1002 $this->add_responsive_control(
1003 'kng_holder_padding',
1004 [
1005 'label' => esc_html__('Padding', 'king-addons'),
1006 'type' => Controls_Manager::DIMENSIONS,
1007 'size_units' => ['px', 'em', '%'],
1008 'selectors' => [
1009 '{{WRAPPER}} .king-addons-rotating-image-tiles__holder' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1010 ],
1011 ]
1012 );
1013
1014 $this->end_controls_section();
1015 }
1016
1017 /**
1018 * Register tiles style controls.
1019 *
1020 * @param bool $is_pro Whether pro controls should be displayed.
1021 *
1022 * @return void
1023 */
1024 public function register_style_tiles_controls(bool $is_pro): void
1025 {
1026 $this->start_controls_section(
1027 'kng_style_tiles_section',
1028 [
1029 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Tiles', 'king-addons'),
1030 'tab' => Controls_Manager::TAB_STYLE,
1031 ]
1032 );
1033
1034 $this->add_control(
1035 'kng_tile_background_color',
1036 [
1037 'label' => esc_html__('Background', 'king-addons'),
1038 'type' => Controls_Manager::COLOR,
1039 'selectors' => [
1040 '{{WRAPPER}} .king-addons-rotating-image-tiles__tile' => 'background-color: {{VALUE}};',
1041 ],
1042 ]
1043 );
1044
1045 $this->add_responsive_control(
1046 'kng_tile_border_radius',
1047 [
1048 'label' => esc_html__('Border Radius', 'king-addons'),
1049 'type' => Controls_Manager::SLIDER,
1050 'size_units' => ['px', '%'],
1051 'range' => [
1052 'px' => [
1053 'min' => 0,
1054 'max' => 120,
1055 ],
1056 '%' => [
1057 'min' => 0,
1058 'max' => 100,
1059 ],
1060 ],
1061 'selectors' => [
1062 '{{WRAPPER}} .king-addons-rotating-image-tiles__tile' => 'border-radius: {{SIZE}}{{UNIT}};',
1063 ],
1064 ]
1065 );
1066
1067 $this->add_group_control(
1068 Group_Control_Box_Shadow::get_type(),
1069 [
1070 'name' => 'kng_tile_box_shadow',
1071 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__tile',
1072 ]
1073 );
1074
1075 if ($is_pro) {
1076 $this->add_control(
1077 'kng_tile_hover_effect',
1078 [
1079 'label' => esc_html__('Hover Effect', 'king-addons'),
1080 'type' => Controls_Manager::SELECT,
1081 'default' => 'none',
1082 'options' => [
1083 'none' => esc_html__('None', 'king-addons'),
1084 'lift' => esc_html__('Lift', 'king-addons'),
1085 'glow' => esc_html__('Glow', 'king-addons'),
1086 'shadow' => esc_html__('Shadow Increase', 'king-addons'),
1087 ],
1088 ]
1089 );
1090 }
1091
1092 $this->end_controls_section();
1093 }
1094
1095 /**
1096 * Register circle style controls.
1097 *
1098 * @param bool $is_pro Whether pro controls should be displayed.
1099 *
1100 * @return void
1101 */
1102 public function register_style_circle_controls(bool $is_pro): void
1103 {
1104 $this->start_controls_section(
1105 'kng_style_circle_section',
1106 [
1107 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Circle Mask', 'king-addons'),
1108 'tab' => Controls_Manager::TAB_STYLE,
1109 ]
1110 );
1111
1112 if ($is_pro) {
1113 $this->add_control(
1114 'kng_use_global_radius',
1115 [
1116 'label' => esc_html__('Use Global Radius', 'king-addons'),
1117 'type' => Controls_Manager::SWITCHER,
1118 'return_value' => 'yes',
1119 'default' => 'no',
1120 ]
1121 );
1122 }
1123
1124 $this->add_control(
1125 'kng_circle_global_radius',
1126 [
1127 'label' => $is_pro ? esc_html__('Global Radius (%)', 'king-addons') : esc_html__('Circle Radius (%)', 'king-addons'),
1128 'type' => Controls_Manager::SLIDER,
1129 'size_units' => ['%'],
1130 'range' => [
1131 '%' => [
1132 'min' => 5,
1133 'max' => 100,
1134 ],
1135 ],
1136 'default' => [
1137 'size' => 40,
1138 'unit' => '%',
1139 ],
1140 ]
1141 );
1142
1143 $this->add_group_control(
1144 Group_Control_Border::get_type(),
1145 [
1146 'name' => 'kng_circle_border',
1147 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__image-layer',
1148 ]
1149 );
1150
1151 $this->add_group_control(
1152 Group_Control_Box_Shadow::get_type(),
1153 [
1154 'name' => 'kng_circle_shadow',
1155 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__image-layer',
1156 ]
1157 );
1158
1159 $this->end_controls_section();
1160 }
1161
1162 /**
1163 * Register image style controls.
1164 *
1165 * @param bool $is_pro Whether pro controls should be displayed.
1166 *
1167 * @return void
1168 */
1169 public function register_style_images_controls(bool $is_pro): void
1170 {
1171 $this->start_controls_section(
1172 'kng_style_images_section',
1173 [
1174 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Images', 'king-addons'),
1175 'tab' => Controls_Manager::TAB_STYLE,
1176 ]
1177 );
1178
1179 $this->add_control(
1180 'kng_image_fit',
1181 [
1182 'label' => esc_html__('Image Fit', 'king-addons'),
1183 'type' => Controls_Manager::SELECT,
1184 'default' => 'cover',
1185 'options' => [
1186 'cover' => esc_html__('Cover', 'king-addons'),
1187 'contain' => esc_html__('Contain', 'king-addons'),
1188 'fill' => esc_html__('Fill', 'king-addons'),
1189 ],
1190 'selectors' => [
1191 '{{WRAPPER}} .king-addons-rotating-image-tiles__image-layer img' => 'object-fit: {{VALUE}};',
1192 ],
1193 ]
1194 );
1195
1196 $this->add_control(
1197 'kng_image_scale_hover',
1198 [
1199 'label' => $is_pro ? esc_html__('Hover Scale', 'king-addons') : esc_html__('Hover Scale (fixed)', 'king-addons'),
1200 'type' => Controls_Manager::SLIDER,
1201 'size_units' => ['px'],
1202 'range' => [
1203 'px' => [
1204 'min' => 1,
1205 'max' => 1.3,
1206 'step' => 0.01,
1207 ],
1208 ],
1209 'default' => [
1210 'size' => 1.05,
1211 'unit' => 'px',
1212 ],
1213 'render_type' => 'none',
1214 ]
1215 );
1216
1217 $this->add_control(
1218 'kng_image_opacity',
1219 [
1220 'label' => esc_html__('Opacity', 'king-addons'),
1221 'type' => Controls_Manager::SLIDER,
1222 'size_units' => ['px'],
1223 'range' => [
1224 'px' => [
1225 'min' => 0,
1226 'max' => 1,
1227 'step' => 0.01,
1228 ],
1229 ],
1230 'default' => [
1231 'size' => 1,
1232 'unit' => 'px',
1233 ],
1234 'selectors' => [
1235 '{{WRAPPER}} .king-addons-rotating-image-tiles__image-layer.is-current img' => 'opacity: {{SIZE}};',
1236 ],
1237 ]
1238 );
1239
1240 $this->add_control(
1241 'kng_image_opacity_hover',
1242 [
1243 'label' => $is_pro ? esc_html__('Opacity on Hover', 'king-addons') : esc_html__('Opacity on Hover (Pro)', 'king-addons'),
1244 'type' => Controls_Manager::SLIDER,
1245 'size_units' => ['px'],
1246 'range' => [
1247 'px' => [
1248 'min' => 0,
1249 'max' => 1,
1250 'step' => 0.01,
1251 ],
1252 ],
1253 'default' => [
1254 'size' => 1,
1255 'unit' => 'px',
1256 ],
1257 'condition' => $is_pro ? [] : ['kng_show_captions' => 'never-match'],
1258 'selectors' => $is_pro ? [
1259 '{{WRAPPER}} .king-addons-rotating-image-tiles__tile:hover .king-addons-rotating-image-tiles__image-layer img' => 'opacity: {{SIZE}};',
1260 ] : [],
1261 ]
1262 );
1263
1264 $this->end_controls_section();
1265 }
1266
1267 /**
1268 * Register overlay style controls.
1269 *
1270 * @param bool $is_pro Whether pro controls should be displayed.
1271 *
1272 * @return void
1273 */
1274 public function register_style_overlay_controls(bool $is_pro): void
1275 {
1276 $this->start_controls_section(
1277 'kng_style_overlay_section',
1278 [
1279 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Overlay', 'king-addons'),
1280 'tab' => Controls_Manager::TAB_STYLE,
1281 ]
1282 );
1283
1284 $this->add_control(
1285 'kng_overlay_color',
1286 [
1287 'label' => esc_html__('Color', 'king-addons'),
1288 'type' => Controls_Manager::COLOR,
1289 'selectors' => [
1290 '{{WRAPPER}} .king-addons-rotating-image-tiles__overlay' => 'background-color: {{VALUE}};',
1291 ],
1292 ]
1293 );
1294
1295 $this->add_control(
1296 'kng_overlay_opacity',
1297 [
1298 'label' => esc_html__('Opacity', 'king-addons'),
1299 'type' => Controls_Manager::SLIDER,
1300 'size_units' => ['px'],
1301 'range' => [
1302 'px' => [
1303 'min' => 0,
1304 'max' => 1,
1305 'step' => 0.01,
1306 ],
1307 ],
1308 'default' => [
1309 'size' => 0,
1310 'unit' => 'px',
1311 ],
1312 'selectors' => [
1313 '{{WRAPPER}} .king-addons-rotating-image-tiles__overlay' => 'opacity: {{SIZE}};',
1314 ],
1315 ]
1316 );
1317
1318 if ($is_pro) {
1319 $this->add_control(
1320 'kng_overlay_blend_mode',
1321 [
1322 'label' => esc_html__('Blend Mode', 'king-addons'),
1323 'type' => Controls_Manager::SELECT,
1324 'default' => 'normal',
1325 'options' => [
1326 'normal' => esc_html__('Normal', 'king-addons'),
1327 'multiply' => esc_html__('Multiply', 'king-addons'),
1328 'screen' => esc_html__('Screen', 'king-addons'),
1329 'overlay' => esc_html__('Overlay', 'king-addons'),
1330 'soft-light' => esc_html__('Soft Light', 'king-addons'),
1331 ],
1332 'selectors' => [
1333 '{{WRAPPER}} .king-addons-rotating-image-tiles__overlay' => 'mix-blend-mode: {{VALUE}};',
1334 ],
1335 ]
1336 );
1337 }
1338
1339 $this->end_controls_section();
1340 }
1341
1342 /**
1343 * Register caption style controls.
1344 *
1345 * @param bool $is_pro Whether pro controls should be displayed.
1346 *
1347 * @return void
1348 */
1349 public function register_style_caption_controls(bool $is_pro): void
1350 {
1351 $this->start_controls_section(
1352 'kng_style_caption_section',
1353 [
1354 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Captions', 'king-addons'),
1355 'tab' => Controls_Manager::TAB_STYLE,
1356 'condition' => [
1357 'kng_show_captions' => 'yes',
1358 ],
1359 ]
1360 );
1361
1362 $this->add_group_control(
1363 Group_Control_Typography::get_type(),
1364 [
1365 'name' => 'kng_caption_typography',
1366 'selector' => '{{WRAPPER}} .king-addons-rotating-image-tiles__caption',
1367 ]
1368 );
1369
1370 $this->add_control(
1371 'kng_caption_color',
1372 [
1373 'label' => esc_html__('Text Color', 'king-addons'),
1374 'type' => Controls_Manager::COLOR,
1375 'selectors' => [
1376 '{{WRAPPER}} .king-addons-rotating-image-tiles__caption' => 'color: {{VALUE}};',
1377 ],
1378 ]
1379 );
1380
1381 $this->add_control(
1382 'kng_caption_background_color',
1383 [
1384 'label' => esc_html__('Background Color', 'king-addons'),
1385 'type' => Controls_Manager::COLOR,
1386 'selectors' => [
1387 '{{WRAPPER}} .king-addons-rotating-image-tiles__caption' => 'background-color: {{VALUE}};',
1388 ],
1389 ]
1390 );
1391
1392 $this->add_responsive_control(
1393 'kng_caption_padding',
1394 [
1395 'label' => esc_html__('Padding', 'king-addons'),
1396 'type' => Controls_Manager::DIMENSIONS,
1397 'size_units' => ['px', 'em', '%'],
1398 'selectors' => [
1399 '{{WRAPPER}} .king-addons-rotating-image-tiles__caption' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1400 ],
1401 ]
1402 );
1403
1404 $this->add_control(
1405 'kng_caption_alignment',
1406 [
1407 'label' => esc_html__('Alignment', 'king-addons'),
1408 'type' => Controls_Manager::CHOOSE,
1409 'options' => [
1410 'left' => [
1411 'title' => esc_html__('Left', 'king-addons'),
1412 'icon' => 'eicon-text-align-left',
1413 ],
1414 'center' => [
1415 'title' => esc_html__('Center', 'king-addons'),
1416 'icon' => 'eicon-text-align-center',
1417 ],
1418 'right' => [
1419 'title' => esc_html__('Right', 'king-addons'),
1420 'icon' => 'eicon-text-align-right',
1421 ],
1422 ],
1423 'default' => 'center',
1424 'selectors' => [
1425 '{{WRAPPER}} .king-addons-rotating-image-tiles__caption' => 'text-align: {{VALUE}};',
1426 ],
1427 ]
1428 );
1429
1430 $this->end_controls_section();
1431 }
1432
1433 /**
1434 * Show pro upsell notice.
1435 *
1436 * @return void
1437 */
1438 public function register_pro_notice_controls(): void
1439 {
1440 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1441 return;
1442 }
1443
1444 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'rotating-image-tiles', [
1445 'Up to 4 images with per-image titles, descriptions, and links',
1446 'Advanced rotation modes: reverse, alternate, random',
1447 'Per-tile circle masks, delay offsets, hover scales',
1448 'Click actions (custom links or lightbox gallery) and hover effects',
1449 'Overlay blend modes, global radius override, and mobile controls',
1450 ]);
1451 }
1452
1453 /**
1454 * Build holder inline style.
1455 *
1456 * @param array<string, mixed> $settings Settings.
1457 *
1458 * @return string
1459 */
1460 public function build_holder_style(array $settings): string
1461 {
1462 $styles = [];
1463
1464 if (!empty($settings['kng_holder_height']['size'])) {
1465 $styles[] = 'height:' . esc_attr($settings['kng_holder_height']['size'] . ($settings['kng_holder_height']['unit'] ?? 'px')) . ';';
1466 }
1467
1468 return empty($styles) ? '' : ' style="' . esc_attr(implode('', $styles)) . '"';
1469 }
1470
1471 /**
1472 * Prepare images payload.
1473 *
1474 * @param array<string, mixed> $settings Settings.
1475 * @param bool $is_pro Whether pro version is active.
1476 *
1477 * @return array<int, array<string, mixed>>
1478 */
1479 public function prepare_images(array $settings, bool $is_pro): array
1480 {
1481 $max_images = $is_pro ? 4 : self::FREE_MAX_IMAGES;
1482 $items = array_slice($settings['kng_images'] ?? [], 0, $max_images);
1483 $prepared = [];
1484
1485 foreach ($items as $item) {
1486 $url = $item['image']['url'] ?? Utils::get_placeholder_image_src();
1487 if (empty($url)) {
1488 $url = Utils::get_placeholder_image_src();
1489 }
1490
1491 $title = $item['title'] ?? '';
1492 $description = $item['description'] ?? '';
1493 $alt = $title ?: $description;
1494
1495 $link = [
1496 'url' => '',
1497 'is_external' => false,
1498 'nofollow' => false,
1499 ];
1500
1501 if ($is_pro && ($item['link_type'] ?? 'none') === 'custom' && !empty($item['link_url']['url'])) {
1502 $link = [
1503 'url' => $item['link_url']['url'],
1504 'is_external' => (bool) ($item['link_url']['is_external'] ?? false),
1505 'nofollow' => (bool) ($item['link_url']['nofollow'] ?? false),
1506 ];
1507 }
1508
1509 $prepared[] = [
1510 'url' => $url,
1511 'title' => $title,
1512 'description' => $description,
1513 'alt' => $alt ?: esc_html__('Image', 'king-addons'),
1514 'link' => $link,
1515 ];
1516 }
1517
1518 return $prepared;
1519 }
1520
1521 /**
1522 * Prepare tiles payload.
1523 *
1524 * @param array<string, mixed> $settings Settings.
1525 * @param bool $is_pro Whether pro version is active.
1526 * @param int $images_length Number of images.
1527 *
1528 * @return array<int, array<string, mixed>>
1529 */
1530 public function prepare_tiles(array $settings, bool $is_pro, int $images_length): array
1531 {
1532 $count = $this->get_int_setting($settings['kng_tiles_count'] ?? 0, 1, $is_pro ? 16 : self::FREE_MAX_TILES);
1533 $tiles = [];
1534
1535 if ($is_pro) {
1536 $raw_tiles = $settings['kng_tiles'] ?? [];
1537 for ($i = 0; $i < $count; $i++) {
1538 $tile = $raw_tiles[$i] ?? [];
1539 $tiles[] = [
1540 'initialIndex' => $this->get_int_setting($tile['initial_image_index'] ?? 0, 0, max(0, $images_length - 1)),
1541 'centerX' => $this->get_float_setting($tile['tile_circle_center_x']['size'] ?? 50, 0, 100),
1542 'centerY' => $this->get_float_setting($tile['tile_circle_center_y']['size'] ?? 50, 0, 100),
1543 'radius' => $this->get_float_setting($tile['tile_circle_radius']['size'] ?? 40, 5, 100),
1544 'delay' => $this->get_int_setting($tile['delay_offset']['size'] ?? 0, 0, 5000),
1545 'hoverScale' => $this->get_float_setting($tile['hover_scale']['size'] ?? 1.05, 1, 1.3),
1546 ];
1547 }
1548 } else {
1549 for ($i = 0; $i < $count; $i++) {
1550 $tiles[] = [
1551 'initialIndex' => $i % max(1, $images_length),
1552 'centerX' => 50,
1553 'centerY' => 50,
1554 'radius' => 40,
1555 'delay' => 0,
1556 'hoverScale' => 1.05,
1557 ];
1558 }
1559 }
1560
1561 return $tiles;
1562 }
1563
1564 /**
1565 * Get integer setting within bounds.
1566 *
1567 * @param mixed $value Raw value.
1568 * @param int $min Minimum allowed.
1569 * @param int $max Maximum allowed.
1570 *
1571 * @return int
1572 */
1573 public function get_int_setting($value, int $min, int $max): int
1574 {
1575 $int = (int) $value;
1576 return max($min, min($max, $int));
1577 }
1578
1579 /**
1580 * Get float setting within bounds.
1581 *
1582 * @param mixed $value Raw value.
1583 * @param float $min Minimum allowed.
1584 * @param float $max Maximum allowed.
1585 *
1586 * @return float
1587 */
1588 public function get_float_setting($value, float $min, float $max): float
1589 {
1590 $float = (float) $value;
1591 return max($min, min($max, $float));
1592 }
1593 }
1594
1595
1596
1597
1598
1599
1600