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 / 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.49, at includes/widgets/Rotating_Image_Tiles/Rotating_Image_Tiles.php

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