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

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

719 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sticky Video 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\Widget_Base;
15
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * Renders a floating sticky video player.
22 */
23 class Sticky_Video extends Widget_Base
24 {
25 /**
26 * Widget slug.
27 *
28 * @return string
29 */
30 public function get_name(): string
31 {
32 return 'king-addons-sticky-video';
33 }
34
35 /**
36 * Widget title.
37 *
38 * @return string
39 */
40 public function get_title(): string
41 {
42 return esc_html__('Sticky Video', 'king-addons');
43 }
44
45 /**
46 * Widget icon.
47 *
48 * @return string
49 */
50 public function get_icon(): string
51 {
52 return 'king-addons-icon king-addons-video';
53 }
54
55 /**
56 * Style dependencies.
57 *
58 * @return array<int, string>
59 */
60 public function get_style_depends(): array
61 {
62 return [
63 KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-video-style',
64 ];
65 }
66
67 /**
68 * Script dependencies.
69 *
70 * @return array<int, string>
71 */
72 public function get_script_depends(): array
73 {
74 return [
75 KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-video-script',
76 ];
77 }
78
79 /**
80 * Categories.
81 *
82 * @return array<int, string>
83 */
84 public function get_categories(): array
85 {
86 return ['king-addons'];
87 }
88
89 /**
90 * Keywords.
91 *
92 * @return array<int, string>
93 */
94 public function get_keywords(): array
95 {
96 return ['video', 'sticky', 'floating', 'youtube', 'vimeo'];
97 }
98
99 /**
100 * Register controls.
101 *
102 * @return void
103 */
104 public function register_controls(): void
105 {
106 $this->register_content_controls();
107 $this->register_layout_controls();
108 $this->register_style_controls();
109 $this->register_pro_notice_controls();
110 }
111
112 /**
113 * Render widget output.
114 *
115 * @return void
116 */
117 public function render(): void
118 {
119 $settings = $this->get_settings_for_display();
120 $position = $settings['kng_position'] ?? 'bottom-right';
121 $allowed_positions = ['bottom-right', 'bottom-left'];
122
123 if (!in_array($position, $allowed_positions, true)) {
124 $position = 'bottom-right';
125 }
126
127 $width_value = isset($settings['kng_width']['size']) ? max(160.0, (float) $settings['kng_width']['size']) : 320.0;
128 $offset_x = isset($settings['kng_offset_x']['size']) ? max(0.0, (float) $settings['kng_offset_x']['size']) : 24.0;
129 $offset_y = isset($settings['kng_offset_y']['size']) ? max(0.0, (float) $settings['kng_offset_y']['size']) : 24.0;
130 $aspect_ratio = $this->normalize_ratio($settings['kng_aspect_ratio'] ?? '16-9');
131
132 $classes = [
133 'king-addons-sticky-video',
134 'king-addons-sticky-video--' . sanitize_html_class($position),
135 'is-visible',
136 ];
137
138 $attrs = [
139 'class' => implode(' ', $classes),
140 'data-position' => $position,
141 'data-offset-x' => (string) $offset_x,
142 'data-offset-y' => (string) $offset_y,
143 'data-trigger-scroll' => '0',
144 'data-delay-ms' => '0',
145 'data-persist-close' => 'no',
146 'data-persist-hours' => '0',
147 'data-device' => 'all',
148 'data-unique' => $this->get_id(),
149 'data-show-close' => (($settings['kng_show_close'] ?? 'yes') === 'yes') ? 'yes' : 'no',
150 'style' => '--kng-sticky-width:' . $width_value . 'px;'
151 . '--kng-sticky-aspect:' . $aspect_ratio . ';'
152 . '--kng-sticky-offset-x:' . $offset_x . 'px;'
153 . '--kng-sticky-offset-y:' . $offset_y . 'px;',
154 ];
155
156 $video_markup = $this->get_video_markup($settings);
157
158 if ($video_markup === '') {
159 echo '<div class="king-addons-sticky-video__notice">' . esc_html__('Please provide a video source.', 'king-addons') . '</div>';
160 return;
161 }
162
163 ?>
164 <div <?php echo $this->compile_attributes($attrs); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
165 <div class="king-addons-sticky-video__frame">
166 <?php if (($settings['kng_show_close'] ?? 'yes') === 'yes') : ?>
167 <button class="king-addons-sticky-video__close" type="button" aria-label="<?php echo esc_attr__('Close sticky video', 'king-addons'); ?>">
168 &times;
169 </button>
170 <?php endif; ?>
171 <div class="king-addons-sticky-video__media">
172 <?php echo $video_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
173 </div>
174 </div>
175 <?php if (!empty($settings['kng_label'])) : ?>
176 <div class="king-addons-sticky-video__label">
177 <?php echo esc_html($settings['kng_label']); ?>
178 </div>
179 <?php endif; ?>
180 </div>
181 <?php
182 }
183
184 /**
185 * Content controls.
186 *
187 * @return void
188 */
189 protected function register_content_controls(): void
190 {
191 $this->start_controls_section(
192 'kng_video_section',
193 [
194 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Video', 'king-addons'),
195 'tab' => Controls_Manager::TAB_CONTENT,
196 ]
197 );
198
199 $this->add_control(
200 'kng_video_source',
201 [
202 'label' => esc_html__('Source', 'king-addons'),
203 'type' => Controls_Manager::SELECT,
204 'default' => 'youtube',
205 'options' => [
206 'youtube' => esc_html__('YouTube (ID)', 'king-addons'),
207 'vimeo' => esc_html__('Vimeo (ID)', 'king-addons'),
208 'self_hosted' => esc_html__('Self-Hosted', 'king-addons'),
209 ],
210 ]
211 );
212
213 $this->add_control(
214 'kng_youtube_id',
215 [
216 'label' => esc_html__('YouTube Video ID', 'king-addons'),
217 'type' => Controls_Manager::TEXT,
218 'placeholder' => esc_html__('e.g. dQw4w9WgXcQ', 'king-addons'),
219 'description' => esc_html__('Use the ID from the YouTube URL (after v=).', 'king-addons'),
220 'condition' => [
221 'kng_video_source' => 'youtube',
222 ],
223 ]
224 );
225
226 $this->add_control(
227 'kng_vimeo_id',
228 [
229 'label' => esc_html__('Vimeo Video ID', 'king-addons'),
230 'type' => Controls_Manager::TEXT,
231 'placeholder' => esc_html__('e.g. 76979871', 'king-addons'),
232 'description' => esc_html__('Use the numeric ID from the Vimeo URL.', 'king-addons'),
233 'condition' => [
234 'kng_video_source' => 'vimeo',
235 ],
236 ]
237 );
238
239 $this->add_control(
240 'kng_self_hosted',
241 [
242 'label' => esc_html__('Self-Hosted Video', 'king-addons'),
243 'type' => Controls_Manager::MEDIA,
244 'media_types' => ['video'],
245 'condition' => [
246 'kng_video_source' => 'self_hosted',
247 ],
248 ]
249 );
250
251 $this->add_control(
252 'kng_start_time',
253 [
254 'label' => esc_html__('Start Time (seconds)', 'king-addons'),
255 'type' => Controls_Manager::NUMBER,
256 'min' => 0,
257 'max' => 3600,
258 'default' => 0,
259 ]
260 );
261
262 $this->add_control(
263 'kng_autoplay',
264 [
265 'label' => esc_html__('Autoplay', 'king-addons'),
266 'type' => Controls_Manager::SWITCHER,
267 'return_value' => 'yes',
268 'default' => 'yes',
269 ]
270 );
271
272 $this->add_control(
273 'kng_muted',
274 [
275 'label' => esc_html__('Mute', 'king-addons'),
276 'type' => Controls_Manager::SWITCHER,
277 'return_value' => 'yes',
278 'default' => 'yes',
279 ]
280 );
281
282 $this->add_control(
283 'kng_loop',
284 [
285 'label' => esc_html__('Loop', 'king-addons'),
286 'type' => Controls_Manager::SWITCHER,
287 'return_value' => 'yes',
288 'default' => '',
289 ]
290 );
291
292 $this->add_control(
293 'kng_show_controls',
294 [
295 'label' => esc_html__('Show Controls', 'king-addons'),
296 'type' => Controls_Manager::SWITCHER,
297 'return_value' => 'yes',
298 'default' => 'yes',
299 ]
300 );
301
302 $this->add_control(
303 'kng_label',
304 [
305 'label' => esc_html__('Label (optional)', 'king-addons'),
306 'type' => Controls_Manager::TEXT,
307 'placeholder' => esc_html__('Now playing', 'king-addons'),
308 ]
309 );
310
311 $this->add_control(
312 'kng_show_close',
313 [
314 'label' => esc_html__('Show Close Button', 'king-addons'),
315 'type' => Controls_Manager::SWITCHER,
316 'return_value' => 'yes',
317 'default' => 'yes',
318 ]
319 );
320
321 $this->end_controls_section();
322 }
323
324 /**
325 * Layout controls.
326 *
327 * @return void
328 */
329 protected function register_layout_controls(): void
330 {
331 $this->start_controls_section(
332 'kng_layout_section',
333 [
334 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Layout', 'king-addons'),
335 'tab' => Controls_Manager::TAB_CONTENT,
336 ]
337 );
338
339 $this->add_control(
340 'kng_position',
341 [
342 'label' => esc_html__('Position', 'king-addons'),
343 'type' => Controls_Manager::SELECT,
344 'default' => 'bottom-right',
345 'options' => [
346 'bottom-right' => esc_html__('Bottom Right', 'king-addons'),
347 'bottom-left' => esc_html__('Bottom Left', 'king-addons'),
348 ],
349 ]
350 );
351
352 $this->add_responsive_control(
353 'kng_width',
354 [
355 'label' => esc_html__('Width', 'king-addons'),
356 'type' => Controls_Manager::SLIDER,
357 'size_units' => ['px'],
358 'range' => [
359 'px' => ['min' => 200, 'max' => 640],
360 ],
361 'default' => [
362 'size' => 320,
363 'unit' => 'px',
364 ],
365 'selectors' => [
366 '{{WRAPPER}} .king-addons-sticky-video' => 'max-width: 90vw;',
367 ],
368 ]
369 );
370
371 $this->add_control(
372 'kng_offset_x',
373 [
374 'label' => esc_html__('Horizontal Offset', 'king-addons'),
375 'type' => Controls_Manager::SLIDER,
376 'size_units' => ['px'],
377 'range' => [
378 'px' => ['min' => 0, 'max' => 200],
379 ],
380 'default' => [
381 'size' => 24,
382 'unit' => 'px',
383 ],
384 ]
385 );
386
387 $this->add_control(
388 'kng_offset_y',
389 [
390 'label' => esc_html__('Vertical Offset', 'king-addons'),
391 'type' => Controls_Manager::SLIDER,
392 'size_units' => ['px'],
393 'range' => [
394 'px' => ['min' => 0, 'max' => 200],
395 ],
396 'default' => [
397 'size' => 24,
398 'unit' => 'px',
399 ],
400 ]
401 );
402
403 $this->add_control(
404 'kng_aspect_ratio',
405 [
406 'label' => esc_html__('Aspect Ratio', 'king-addons'),
407 'type' => Controls_Manager::SELECT,
408 'default' => '16-9',
409 'options' => [
410 '16-9' => esc_html__('16:9', 'king-addons'),
411 '4-3' => esc_html__('4:3', 'king-addons'),
412 '1-1' => esc_html__('1:1', 'king-addons'),
413 '9-16' => esc_html__('9:16', 'king-addons'),
414 ],
415 ]
416 );
417
418 $this->end_controls_section();
419 }
420
421 /**
422 * Style controls.
423 *
424 * @return void
425 */
426 protected function register_style_controls(): void
427 {
428 $this->start_controls_section(
429 'kng_container_style',
430 [
431 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Container', 'king-addons'),
432 'tab' => Controls_Manager::TAB_STYLE,
433 ]
434 );
435
436 $this->add_control(
437 'kng_container_bg',
438 [
439 'label' => esc_html__('Background', 'king-addons'),
440 'type' => Controls_Manager::COLOR,
441 'selectors' => [
442 '{{WRAPPER}} .king-addons-sticky-video' => 'background-color: {{VALUE}};',
443 ],
444 ]
445 );
446
447 $this->add_group_control(
448 Group_Control_Border::get_type(),
449 [
450 'name' => 'kng_container_border',
451 'selector' => '{{WRAPPER}} .king-addons-sticky-video',
452 ]
453 );
454
455 $this->add_control(
456 'kng_container_radius',
457 [
458 'label' => esc_html__('Border Radius', 'king-addons'),
459 'type' => Controls_Manager::SLIDER,
460 'size_units' => ['px', '%'],
461 'range' => [
462 'px' => ['min' => 0, 'max' => 40],
463 '%' => ['min' => 0, 'max' => 50],
464 ],
465 'default' => [
466 'size' => 12,
467 'unit' => 'px',
468 ],
469 'selectors' => [
470 '{{WRAPPER}} .king-addons-sticky-video' => 'border-radius: {{SIZE}}{{UNIT}};',
471 ],
472 ]
473 );
474
475 $this->add_group_control(
476 Group_Control_Box_Shadow::get_type(),
477 [
478 'name' => 'kng_container_shadow',
479 'selector' => '{{WRAPPER}} .king-addons-sticky-video',
480 ]
481 );
482
483 $this->end_controls_section();
484
485 $this->start_controls_section(
486 'kng_label_style',
487 [
488 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Label', 'king-addons'),
489 'tab' => Controls_Manager::TAB_STYLE,
490 'condition' => [
491 'kng_label!' => '',
492 ],
493 ]
494 );
495
496 $this->add_group_control(
497 Group_Control_Typography::get_type(),
498 [
499 'name' => 'kng_label_typography',
500 'selector' => '{{WRAPPER}} .king-addons-sticky-video__label',
501 ]
502 );
503
504 $this->add_control(
505 'kng_label_color',
506 [
507 'label' => esc_html__('Text Color', 'king-addons'),
508 'type' => Controls_Manager::COLOR,
509 'selectors' => [
510 '{{WRAPPER}} .king-addons-sticky-video__label' => 'color: {{VALUE}};',
511 ],
512 ]
513 );
514
515 $this->end_controls_section();
516
517 $this->start_controls_section(
518 'kng_close_style',
519 [
520 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Close Button', 'king-addons'),
521 'tab' => Controls_Manager::TAB_STYLE,
522 'condition' => [
523 'kng_show_close' => 'yes',
524 ],
525 ]
526 );
527
528 $this->add_control(
529 'kng_close_color',
530 [
531 'label' => esc_html__('Icon Color', 'king-addons'),
532 'type' => Controls_Manager::COLOR,
533 'selectors' => [
534 '{{WRAPPER}} .king-addons-sticky-video__close' => 'color: {{VALUE}};',
535 ],
536 ]
537 );
538
539 $this->add_control(
540 'kng_close_bg',
541 [
542 'label' => esc_html__('Background', 'king-addons'),
543 'type' => Controls_Manager::COLOR,
544 'selectors' => [
545 '{{WRAPPER}} .king-addons-sticky-video__close' => 'background-color: {{VALUE}};',
546 ],
547 ]
548 );
549
550 $this->add_control(
551 'kng_close_radius',
552 [
553 'label' => esc_html__('Radius', 'king-addons'),
554 'type' => Controls_Manager::SLIDER,
555 'size_units' => ['px', '%'],
556 'range' => [
557 'px' => ['min' => 0, 'max' => 20],
558 '%' => ['min' => 0, 'max' => 50],
559 ],
560 'selectors' => [
561 '{{WRAPPER}} .king-addons-sticky-video__close' => 'border-radius: {{SIZE}}{{UNIT}};',
562 ],
563 ]
564 );
565
566 $this->end_controls_section();
567 }
568
569 /**
570 * Render pro upgrade section.
571 *
572 * @return void
573 */
574 public function register_pro_notice_controls(): void
575 {
576 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
577 Core::renderProFeaturesSection($this, '', Controls_Manager::RAW_HTML, 'sticky-video', [
578 'Top corner placements with per-axis offsets',
579 'Delayed or scroll-triggered reveal',
580 'Device targeting and close persistence',
581 'CTA button beneath the player',
582 ]);
583 }
584 }
585
586 /**
587 * Build video markup.
588 *
589 * @param array<string, mixed> $settings Settings.
590 *
591 * @return string
592 */
593 protected function get_video_markup(array $settings): string
594 {
595 $source = $settings['kng_video_source'] ?? 'youtube';
596 $autoplay = (($settings['kng_autoplay'] ?? 'yes') === 'yes');
597 $muted = (($settings['kng_muted'] ?? 'yes') === 'yes');
598 $loop = (($settings['kng_loop'] ?? '') === 'yes');
599 $controls = (($settings['kng_show_controls'] ?? 'yes') === 'yes');
600 $start = max(0, (int) ($settings['kng_start_time'] ?? 0));
601
602 if ($source === 'youtube' && !empty($settings['kng_youtube_id'])) {
603 $video_id = preg_replace('/[^A-Za-z0-9_-]/', '', (string) $settings['kng_youtube_id']);
604 $params = [
605 'rel' => '0',
606 'playsinline' => '1',
607 'autoplay' => $autoplay ? '1' : '0',
608 'mute' => $muted ? '1' : '0',
609 'controls' => $controls ? '1' : '0',
610 ];
611
612 if ($start > 0) {
613 $params['start'] = (string) $start;
614 }
615
616 if ($loop) {
617 $params['loop'] = '1';
618 $params['playlist'] = $video_id;
619 }
620
621 $src = 'https://www.youtube.com/embed/' . rawurlencode($video_id) . '?' . http_build_query($params);
622
623 return '<iframe src="' . esc_url($src) . '" allow="autoplay; encrypted-media; picture-in-picture" allowfullscreen title="' . esc_attr__('Sticky video', 'king-addons') . '"></iframe>';
624 }
625
626 if ($source === 'vimeo' && !empty($settings['kng_vimeo_id'])) {
627 $video_id = preg_replace('/[^0-9]/', '', (string) $settings['kng_vimeo_id']);
628 $params = [
629 'autoplay' => $autoplay ? '1' : '0',
630 'muted' => $muted ? '1' : '0',
631 'loop' => $loop ? '1' : '0',
632 'title' => '0',
633 'byline' => '0',
634 'portrait' => '0',
635 'controls' => $controls ? '1' : '0',
636 ];
637
638 $src = 'https://player.vimeo.com/video/' . rawurlencode($video_id);
639 $query = http_build_query($params);
640 $fragment = $start > 0 ? '#t=' . $start . 's' : '';
641
642 return '<iframe src="' . esc_url($src . '?' . $query . $fragment) . '" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="' . esc_attr__('Sticky video', 'king-addons') . '"></iframe>';
643 }
644
645 if ($source === 'self_hosted' && !empty($settings['kng_self_hosted']['url'])) {
646 $video_url = esc_url($settings['kng_self_hosted']['url']);
647 $attrs = [
648 'playsinline',
649 ];
650
651 if ($autoplay) {
652 $attrs[] = 'autoplay';
653 }
654
655 if ($muted) {
656 $attrs[] = 'muted';
657 }
658
659 if ($loop) {
660 $attrs[] = 'loop';
661 }
662
663 if ($controls) {
664 $attrs[] = 'controls';
665 }
666
667 $attr_string = implode(' ', $attrs);
668
669 return '<video src="' . $video_url . '" ' . esc_attr($attr_string) . '></video>';
670 }
671
672 return '';
673 }
674
675 /**
676 * Compile attributes for output.
677 *
678 * @param array<string, string> $attrs Attributes.
679 *
680 * @return string
681 */
682 protected function compile_attributes(array $attrs): string
683 {
684 $compiled = [];
685
686 foreach ($attrs as $key => $value) {
687 if ($value === '') {
688 continue;
689 }
690
691 $compiled[] = esc_attr($key) . '="' . esc_attr((string) $value) . '"';
692 }
693
694 return implode(' ', $compiled);
695 }
696
697 /**
698 * Normalize ratio label to percentage padding.
699 *
700 * @param string $ratio Ratio label.
701 *
702 * @return string
703 */
704 protected function normalize_ratio(string $ratio): string
705 {
706 $map = [
707 '16-9' => '56.25%',
708 '4-3' => '75%',
709 '1-1' => '100%',
710 '9-16' => '177.78%',
711 ];
712
713 return $map[$ratio] ?? '56.25%';
714 }
715 }
716
717
718
719