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 / 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.78, at includes/widgets/Sticky_Video/Sticky_Video.php

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