PluginProbe
MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor / 0.6.0
MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor v0.6.0
0.6.0 0.5.4 0.5.3 0.5.2 0.5.1 0.5.0 0.4.0 0.3.3 0.3.2 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.1.0 0.1.1 0.1.2 0.2.0 0.2.1 0.2.2 0.2.3 0.3.0 0.3.1
marqueex / includes / Integrations / Elementor / Widgets / ImageMarquee.php

ImageMarquee.php in MarqueeX – Smooth Marquee Slider, News Ticker & Post Marquee for Block Editor & Elementor 0.6.0, at includes/Integrations/Elementor/Widgets/ImageMarquee.php

903 lines 30.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Wpxero\Marqueex\Integrations\Elementor\Widgets;
4
5 use Elementor\Widget_Base;
6 use Elementor\Controls_Manager;
7 use Elementor\Group_Control_Typography;
8 use Elementor\Group_Control_Border;
9 use Elementor\Group_Control_Box_Shadow;
10 use Elementor\Group_Control_Background;
11 use Elementor\Group_Control_Text_Shadow;
12 use Elementor\Icons_Manager;
13 use Elementor\Utils;
14 use Wpxero\Marqueex\Traits\MarqueeSource;
15
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * MarqueeX Image Marquee Widget for Elementor
22 */
23 class ImageMarquee extends Widget_Base {
24
25 use MarqueeSource;
26
27 /**
28 * Get widget name
29 */
30 public function get_name() {
31 return 'marqueex-image-marquee';
32 }
33
34 /**
35 * Get widget title
36 */
37 public function get_title() {
38 return __('Image Marquee', 'marqueex');
39 }
40
41 /**
42 * Get widget icon
43 */
44 public function get_icon() {
45 return 'eicon-featured-image eicon-marqueex';
46 }
47
48 /**
49 * Get widget categories
50 */
51 public function get_categories() {
52 return ['marqueex'];
53 }
54
55 /**
56 * Enqueue widget scripts and styles
57 */
58 public function get_script_depends() {
59 return ['marqueex-image-marquee'];
60 }
61
62 public function get_style_depends() {
63 return ['marqueex-image-marquee'];
64 }
65 /**
66 * Get widget keywords
67 */
68 public function get_keywords() {
69 return ['marquee', 'ticker', 'marquee', 'posts', 'slider', 'scroll'];
70 }
71
72 /**
73 * Register widget controls
74 */
75 protected function register_controls() {
76 // Content Type Section
77 $this->start_controls_section(
78 'content_type_section',
79 [
80 'label' => __('Images', 'marqueex'),
81 'tab' => Controls_Manager::TAB_CONTENT,
82 ]
83 );
84
85 $this->add_control(
86 'content_type',
87 [
88 'label' => __('Content Type', 'marqueex'),
89 'type' => Controls_Manager::SELECT,
90 'default' => 'custom',
91 'options' => [
92 'posts' => __('Dynamic Posts', 'marqueex'),
93 'custom' => __('Custom Images', 'marqueex'),
94 ],
95 'condition' => [
96 'content_type' => 'never_display',
97 ],
98 ]
99 );
100
101 $this->add_control(
102 'image_content_custom',
103 [
104 'label' => __('Image Content', 'marqueex'),
105 'type' => Controls_Manager::REPEATER,
106 'condition' => [
107 'content_type' => 'custom',
108 ],
109 'fields' => [
110 [
111 'name' => 'image',
112 'label' => __('Image', 'marqueex'),
113 'type' => Controls_Manager::MEDIA,
114 'default' => [
115 'url' => Utils::get_placeholder_image_src(),
116 ],
117 'description' => __('Choose an image for the marquee.', 'marqueex'),
118 ],
119 [
120 'name' => 'alt_text',
121 'label' => __('Alt Text', 'marqueex'),
122 'type' => Controls_Manager::TEXT,
123 'default' => __('Image', 'marqueex'),
124 'description' => __('Enter alt text for accessibility.', 'marqueex'),
125 ],
126 [
127 'name' => 'link',
128 'label' => __('Link', 'marqueex'),
129 'type' => Controls_Manager::URL,
130 'default' => [
131 'url' => '',
132 'is_external' => '',
133 'nofollow' => '',
134 ],
135 'description' => __('Enter the link for the image.', 'marqueex'),
136 ],
137 ],
138 'condition' => [
139 'content_type' => 'custom',
140 ],
141 'title_field' => '{{{ alt_text }}}',
142 'default' => [
143 [
144 'image' => [
145 'url' => Utils::get_placeholder_image_src(),
146 ],
147 'alt_text' => __('Sample Image 1', 'marqueex'),
148 'link' => [
149 'url' => '',
150 'is_external' => '',
151 'nofollow' => '',
152 ],
153 ],
154 [
155 'image' => [
156 'url' => Utils::get_placeholder_image_src(),
157 ],
158 'alt_text' => __('Sample Image 2', 'marqueex'),
159 'link' => [
160 'url' => '',
161 'is_external' => '',
162 'nofollow' => '',
163 ],
164 ],
165 [
166 'image' => [
167 'url' => Utils::get_placeholder_image_src(),
168 ],
169 'alt_text' => __('Sample Image 3', 'marqueex'),
170 'link' => [
171 'url' => '',
172 'is_external' => '',
173 'nofollow' => '',
174 ],
175 ],
176 ],
177 ]
178 );
179 $this->add_responsive_control(
180 'item_spacing',
181 [
182 'label' => __('Item Spacing', 'marqueex'),
183 'type' => Controls_Manager::SLIDER,
184 'size_units' => ['px', 'em', 'rem'],
185 'range' => [
186 'px' => [
187 'min' => 0,
188 'max' => 100,
189 'step' => 1,
190 ],
191 'em' => [
192 'min' => 0,
193 'max' => 10,
194 'step' => 0.1,
195 ],
196 'rem' => [
197 'min' => 0,
198 'max' => 10,
199 'step' => 0.1,
200 ],
201 ],
202 'default' => [
203 'unit' => 'rem',
204 'size' => 2,
205 ],
206 'selectors' => [
207 '{{WRAPPER}} .marqueex-marquee-track-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
208 '{{WRAPPER}} .marqueex-marquee-track' => 'gap: {{SIZE}}{{UNIT}};',
209 '{{WRAPPER}} .marqueex-marquee-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
210 ],
211 'separator' => 'before',
212 // 'condition' => [
213 // 'content_type' => 'never_display',
214 // ],
215 ]
216 );
217 $this->end_controls_section();
218
219 // Query Section
220 $this->start_controls_section(
221 'query_section',
222 [
223 'label' => __('Query', 'marqueex'),
224 'tab' => Controls_Manager::TAB_CONTENT,
225 'condition' => [
226 'content_type' => 'posts',
227 ],
228 ]
229 );
230
231 $this->add_control(
232 'post_type',
233 [
234 'label' => __('Post Type', 'marqueex'),
235 'type' => Controls_Manager::SELECT,
236 'default' => 'post',
237 'options' => $this->get_post_types(),
238 ]
239 );
240
241 $this->add_control(
242 'posts_per_page',
243 [
244 'label' => __('Number of Posts', 'marqueex'),
245 'type' => Controls_Manager::NUMBER,
246 'default' => 6,
247 'min' => 1,
248 'max' => 50,
249 ]
250 );
251
252 $this->add_control(
253 'orderby',
254 [
255 'label' => __('Order By', 'marqueex'),
256 'type' => Controls_Manager::SELECT,
257 'default' => 'date',
258 'options' => [
259 'date' => __('Date', 'marqueex'),
260 'title' => __('Title', 'marqueex'),
261 'rand' => __('Random', 'marqueex'),
262 'menu_order' => __('Menu Order', 'marqueex'),
263 ],
264 ]
265 );
266
267 $this->add_control(
268 'order',
269 [
270 'label' => __('Order', 'marqueex'),
271 'type' => Controls_Manager::SELECT,
272 'default' => 'desc',
273 'options' => [
274 'asc' => __('ASC', 'marqueex'),
275 'desc' => __('DESC', 'marqueex'),
276 ],
277 'condition' => [
278 'orderby!' => 'rand',
279 ],
280 ]
281 );
282
283 $this->add_control(
284 'category_ids',
285 [
286 'label' => __('Categories', 'marqueex'),
287 'type' => Controls_Manager::SELECT2,
288 'multiple' => true,
289 'options' => $this->get_post_categories(),
290 'condition' => [
291 'post_type' => 'post',
292 ],
293 ]
294 );
295
296 $this->end_controls_section();
297
298
299
300
301 // Marquee Settings Section
302 $this->start_controls_section(
303 'marquee_settings_section',
304 [
305 'label' => __('Marquee Settings', 'marqueex'),
306 'tab' => Controls_Manager::TAB_CONTENT,
307 ]
308 );
309
310 $this->add_control(
311 'speed',
312 [
313 'label' => __('Speed', 'marqueex'),
314 'type' => Controls_Manager::SLIDER,
315 'size_units' => ['px/s'],
316 'range' => [
317 'px' => [
318 'min' => 10,
319 'max' => 100,
320 'step' => 1,
321 ],
322 ],
323 'default' => [
324 'unit' => 'px',
325 'size' => 10,
326 ],
327 ]
328 );
329 $this->add_control(
330 'direction_vertical',
331 [
332 'label' => __('Direction Vertical', 'marqueex'),
333 'type' => Controls_Manager::SWITCHER,
334 'default' => 'no',
335 'options' => [
336 'no' => __('Horizontal', 'marqueex'),
337 'yes' => __('Vertical', 'marqueex'),
338 ],
339 ]
340 );
341
342 $this->add_control(
343 'pause_on_hover',
344 [
345 'label' => __('Pause on Hover', 'marqueex'),
346 'type' => Controls_Manager::SWITCHER,
347 'label_on' => __('Yes', 'marqueex'),
348 'label_off' => __('No', 'marqueex'),
349 'return_value' => 'yes',
350 'default' => 'yes',
351 ]
352 );
353
354 $this->add_control(
355 'reverse_direction',
356 [
357 'label' => __('Reverse Direction', 'marqueex'),
358 'type' => Controls_Manager::SWITCHER,
359 'label_on' => __('Yes', 'marqueex'),
360 'label_off' => __('No', 'marqueex'),
361 'return_value' => 'yes',
362 'default' => 'no',
363 ]
364 );
365
366 $this->end_controls_section();
367
368
369 // Image Settings Section
370 $this->start_controls_section(
371 'image_settings_section',
372 [
373 'label' => __('Image', 'marqueex'),
374 'tab' => Controls_Manager::TAB_STYLE,
375 'condition' => [
376 'content_type' => 'custom',
377 ],
378 ]
379 );
380
381 $this->add_responsive_control(
382 'image_width',
383 [
384 'label' => __('Image Width', 'marqueex'),
385 'type' => Controls_Manager::SLIDER,
386 'size_units' => ['px', '%', 'em', 'rem', 'vw'],
387 'range' => [
388 'px' => [
389 'min' => 50,
390 'max' => 500,
391 'step' => 1,
392 ],
393 '%' => [
394 'min' => 10,
395 'max' => 100,
396 'step' => 1,
397 ],
398 ],
399 'default' => [
400 'unit' => 'px',
401 'size' => 150,
402 ],
403 'selectors' => [
404 '{{WRAPPER}} .marqueex-image-item' => 'width: {{SIZE}}{{UNIT}};',
405 ],
406 ]
407 );
408
409 $this->add_responsive_control(
410 'image_height',
411 [
412 'label' => __('Image Height', 'marqueex'),
413 'type' => Controls_Manager::SLIDER,
414 'size_units' => ['px', '%', 'em', 'rem', 'vh'],
415 'range' => [
416 'px' => [
417 'min' => 50,
418 'max' => 500,
419 'step' => 1,
420 ],
421 '%' => [
422 'min' => 10,
423 'max' => 100,
424 'step' => 1,
425 ],
426 ],
427 'default' => [
428 'unit' => 'px',
429 'size' => 150,
430 ],
431 'selectors' => [
432 '{{WRAPPER}} .marqueex-image-item' => 'height: {{SIZE}}{{UNIT}};',
433 ],
434 ]
435 );
436 //border control
437 $this->add_group_control(
438 Group_Control_Border::get_type(),
439 [
440 'name' => 'image_border',
441 'label' => __('Border', 'marqueex'),
442 'selector' => '{{WRAPPER}} .marqueex-image-item',
443 ]
444 );
445
446 $this->add_responsive_control(
447 'image_border_radius',
448 [
449 'label' => __('Border Radius', 'marqueex'),
450 'type' => Controls_Manager::DIMENSIONS,
451 'size_units' => ['px', '%', 'em'],
452 'selectors' => [
453 '{{WRAPPER}} .marqueex-image-item' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
454 ],
455 ]
456 );
457
458 $this->add_group_control(
459 Group_Control_Box_Shadow::get_type(),
460 [
461 'name' => 'image_box_shadow',
462 'label' => __('Box Shadow', 'marqueex'),
463 'selector' => '{{WRAPPER}} .marqueex-image-item',
464 ]
465 );
466
467 $this->add_responsive_control(
468 'image_padding',
469 [
470 'label' => __('Padding', 'marqueex'),
471 'type' => Controls_Manager::DIMENSIONS,
472 'size_units' => ['px', 'em', '%'],
473 'selectors' => [
474 '{{WRAPPER}} .marqueex-image-item' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
475 ],
476 ]
477 );
478
479 $this->add_responsive_control(
480 'image_margin',
481 [
482 'label' => __('Margin', 'marqueex'),
483 'type' => Controls_Manager::DIMENSIONS,
484 'size_units' => ['px', 'em', '%'],
485 'selectors' => [
486 '{{WRAPPER}} .marqueex-image-item' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
487 ],
488 ]
489 );
490
491
492
493 $this->add_control(
494 'image_object_fit',
495 [
496 'label' => __('Object Fit', 'marqueex'),
497 'type' => Controls_Manager::SELECT,
498 'default' => 'cover',
499 'options' => [
500 'fill' => __('Fill', 'marqueex'),
501 'contain' => __('Contain', 'marqueex'),
502 'cover' => __('Cover', 'marqueex'),
503 'none' => __('None', 'marqueex'),
504 'scale-down' => __('Scale Down', 'marqueex'),
505 ],
506 'selectors' => [
507 '{{WRAPPER}} .marqueex-image-item' => 'object-fit: {{VALUE}};',
508 ],
509 ]
510 );
511
512 $this->add_control(
513 'image_object_position',
514 [
515 'label' => __('Object Position', 'marqueex'),
516 'type' => Controls_Manager::SELECT,
517 'default' => 'center center',
518 'options' => [
519 'center center' => __('Center Center', 'marqueex'),
520 'center left' => __('Center Left', 'marqueex'),
521 'center right' => __('Center Right', 'marqueex'),
522 'top center' => __('Top Center', 'marqueex'),
523 'top left' => __('Top Left', 'marqueex'),
524 'top right' => __('Top Right', 'marqueex'),
525 'bottom center' => __('Bottom Center', 'marqueex'),
526 'bottom left' => __('Bottom Left', 'marqueex'),
527 'bottom right' => __('Bottom Right', 'marqueex'),
528 ],
529 'selectors' => [
530 '{{WRAPPER}} .marqueex-image-item' => 'object-position: {{VALUE}};',
531 ],
532 'condition' => [
533 'image_object_fit!' => 'fill',
534 ],
535 ]
536 );
537
538 $this->end_controls_section();
539 }
540
541 /**
542 * Prepare widget configuration from settings
543 *
544 * Extracts and processes all widget settings into a clean configuration array.
545 *
546 * @param array $settings Widget settings from Elementor
547 * @return array Processed configuration array
548 */
549 private function prepare_widget_config($settings) {
550 return [
551 'unique_id' => 'marqueex-ticker-' . $this->get_id(),
552 'speed' => max(10, min(100, intval($settings['speed']['size'] ?? 10))), // Security: Clamp speed values
553 'marquee_direction' => $settings['marquee_direction'] ?? 'horizontal',
554 'pause_on_hover' => $settings['pause_on_hover'] === 'yes' ? 'true' : 'false',
555 'reverse_direction' => $settings['reverse_direction'] === 'yes',
556 'content_type' => in_array($settings['content_type'] ?? 'posts', ['posts', 'custom']) ? $settings['content_type'] : 'posts',
557 ];
558 }
559
560 /**
561 * Render ticker content based on content type
562 *
563 * @param array $settings Widget settings
564 * @param array $config Widget configuration
565 */
566 private function render_ticker_content_by_type($settings, $config) {
567 if ($config['content_type'] === 'posts') {
568 $this->render_dynamic_posts_content($settings);
569 } else {
570 $this->render_custom_image_content($settings);
571 }
572 }
573
574 /**
575 * Render dynamic posts content
576 *
577 * @param array $settings Widget settings
578 */
579 private function render_dynamic_posts_content($settings) {
580 $query_args = $this->get_query_args($settings);
581 $posts = get_posts($query_args);
582 $this->render_ticker_content($settings, $posts);
583 }
584
585 /**
586 * Render custom image content
587 *
588 * @param array $settings Widget settings
589 */
590 private function render_custom_image_content($settings) {
591 $image_content = $settings['image_content_custom'] ?? [];
592
593 if (empty($image_content)) {
594 return;
595 }
596
597 echo '<div class="marqueex-marquee-wrapper">';
598 $total_items = count($image_content);
599 $image_content = $this->ensure_minimum_posts($image_content);
600
601 foreach ($image_content as $index => $item) {
602 $image = $item['image'] ?? [];
603 $alt_text = $item['alt_text'] ?? '';
604 $link = $item['link'] ?? [];
605
606 if (empty($image['url'])) {
607 continue;
608 }
609
610 echo '<div class="marqueex-image-item">';
611
612 // Render image with optional link
613 if (!empty($link['url'])) {
614 $rel_attrs = [];
615
616 if ($link['is_external']) {
617 $rel_attrs[] = 'noopener noreferrer';
618 }
619 if ($link['nofollow']) {
620 $rel_attrs[] = 'nofollow';
621 }
622
623 $rel_value = implode(' ', $rel_attrs);
624
625 echo '<a href="' . esc_url($link['url']) . '"'
626 . ($link['is_external'] ? ' target="_blank"' : '')
627 . ($rel_value ? ' rel="' . esc_attr($rel_value) . '"' : '')
628 . ' class="marqueex-image-link" aria-label="' . esc_attr($alt_text) . '">';
629 echo '<img src="' . esc_url($image['url']) . '" alt="' . esc_attr($alt_text) . '" class="marqueex-marquee-image" loading="lazy" />';
630 echo '</a>';
631 } else {
632 echo '<img src="' . esc_url($image['url']) . '" alt="' . esc_attr($alt_text) . '" class="marqueex-marquee-image" loading="lazy" />';
633 }
634
635 echo '</div>';
636 }
637 echo '</div>';
638 }
639
640
641 /**
642 * Prepare link configuration
643 *
644 * @param array $settings Widget settings
645 * @return array Link configuration
646 */
647 private function prepare_link_config($settings) {
648 return [
649 'enable_links' => $settings['enable_links'] ?? 'yes',
650 'open_in_new_tab' => $settings['open_in_new_tab'] ?? 'no'
651 ];
652 }
653
654 /**
655 * Render single image item from post
656 *
657 * @param object $post Post object
658 * @param array $settings Widget settings
659 * @param array $link_config Link configuration
660 */
661 private function render_single_news_item($post, $settings, $link_config) {
662 if (!$post instanceof \WP_Post) {
663 return;
664 }
665
666 $title = get_the_title($post);
667 $url = $this->get_post_url($post);
668
669 // Get featured image
670 $image_id = get_post_thumbnail_id($post);
671 if (!$image_id) {
672 return; // Skip posts without featured images
673 }
674
675 $image_url = wp_get_attachment_image_url($image_id, 'medium_large');
676 if (!$image_url) {
677 return;
678 }
679
680 // Security: Trim title if enabled for alt text
681 $alt_text = $title;
682 if ($settings['title_trim_enable'] === 'yes') {
683 $alt_text = wp_trim_words($title, $settings['title_trim_length'], '...');
684 }
685
686 echo '<div class="marqueex-image-item">';
687
688 if ($link_config['enable_links'] === 'yes' && !empty($url)) {
689 echo '<a href="' . esc_url($url) . '"'
690 . ($link_config['open_in_new_tab'] === 'yes' ? ' target="_blank" rel="noopener noreferrer"' : '')
691 . ' class="marqueex-image-link" aria-label="' . esc_attr($alt_text) . '">';
692 echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($alt_text) . '" class="marqueex-marquee-image" loading="lazy" />';
693 echo '</a>';
694 } else {
695 echo '<img src="' . esc_url($image_url) . '" alt="' . esc_attr($alt_text) . '" class="marqueex-marquee-image" loading="lazy" />';
696 }
697
698 echo '</div>';
699 }
700
701 /**
702 * Render news ticker content with posts and separators
703 *
704 * Handles the rendering of news items with proper title trimming,
705 * link generation, and separator insertion between items.
706 *
707 * @param array $settings Widget settings
708 * @param array $posts Array of post objects
709 */
710 private function render_ticker_content($settings, $posts = []) {
711 // Ensure we have posts to display
712 $posts = $this->ensure_minimum_posts($posts);
713
714 // Prepare link configuration
715 $link_config = $this->prepare_link_config($settings);
716
717 echo '<div class="marqueex-marquee-wrapper">';
718
719 // Render each post with separators
720 $total_posts = count($posts);
721 foreach ($posts as $index => $post) {
722 echo '<div class="marqueex-news-item">';
723 if ($settings['direction_vertical'] === 'yes') {
724 $this->render_separator($settings, $post);
725 }
726 $this->render_single_news_item($post, $settings, $link_config);
727 echo '</div>';
728
729 // Only render separator if not the last item
730 if ($settings['direction_vertical'] !== 'yes') {
731 if ($index < $total_posts - 1) {
732 $this->render_separator($settings, $post);
733 }
734 }
735 }
736
737 echo '</div>';
738 }
739
740 /**
741 * Render separator based on type
742 *
743 * @param array $settings Widget settings
744 * @param WP_Post $post Current post object
745 */
746 private function render_separator($settings, $post) {
747 $separator_type = $settings['separator_type'] ?? 'none';
748
749 if ($separator_type === 'none') {
750 return;
751 }
752
753 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
754 $separator_classes = 'marqueex-separator marqueex-separator-' . esc_attr($separator_type) . ' marqueex-separator-preset-' . esc_attr($separator_preset);
755
756 echo '<span class="' . esc_attr($separator_classes) . '">';
757
758 switch ($separator_type) {
759 case 'icon':
760 $separator_icon = $settings['separator_icon'] ?? [];
761 if (!empty($separator_icon['value'])) {
762 echo '<span class="marqueex-separator-icon">';
763 \Elementor\Icons_Manager::render_icon($separator_icon, ['aria-hidden' => 'true']);
764 echo '</span>';
765 }
766 break;
767
768 case 'text':
769 $separator_text = $settings['separator_text'] ?? '|';
770 echo '<span class="marqueex-separator-text">' . esc_html($separator_text) . '</span>';
771 break;
772
773 case 'date':
774 echo '<span class="marqueex-separator-date">' . esc_html(get_the_date('', $post)) . '</span>';
775 break;
776
777 case 'featured_image':
778 if (has_post_thumbnail($post)) {
779 echo '<span class="marqueex-separator-feature-image">';
780 echo get_the_post_thumbnail($post, 'thumbnail');
781 echo '</span>';
782 }
783 break;
784 }
785
786 echo '</span>';
787 }
788 private function render_icons($settings, $icon) {
789 $separator_type = $settings['separator_type'] ?? 'icon';
790
791 if ($separator_type === 'none') {
792 return;
793 }
794 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
795 $separator_classes = 'marqueex-separator marqueex-separator-icon marqueex-separator-preset-' . esc_attr($separator_preset);
796
797 if (!empty($icon['value'])) {
798 echo '<span class="' . esc_attr($separator_classes) . '">';
799 echo '<span class="marqueex-separator-icon">';
800 \Elementor\Icons_Manager::render_icon($icon, ['aria-hidden' => 'true']);
801 echo '</span>';
802 echo '</span>';
803 }
804 }
805
806 /**
807 * Render separator for custom content
808 *
809 * @param array $settings Widget settings
810 */
811 private function render_separator_for_custom($settings) {
812 $separator_type = $settings['separator_type'] ?? 'none';
813
814 if ($separator_type === 'none') {
815 return;
816 }
817
818 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
819 $separator_classes = 'marqueex-separator marqueex-separator-' . esc_attr($separator_type) . ' marqueex-separator-preset-' . esc_attr($separator_preset);
820
821 echo '<span class="' . esc_attr($separator_classes) . '">';
822
823 switch ($separator_type) {
824 case 'icon':
825 $separator_icon = $settings['separator_icon'] ?? [];
826 if (!empty($separator_icon['value'])) {
827 echo '<span class="marqueex-separator-icon">';
828 \Elementor\Icons_Manager::render_icon($separator_icon, ['aria-hidden' => 'true']);
829 echo '</span>';
830 }
831 break;
832
833 case 'text':
834 $separator_text = $settings['separator_text'] ?? '|';
835 echo '<span class="marqueex-separator-text">' . esc_html($separator_text) . '</span>';
836 break;
837
838 case 'date':
839 echo '<span class="marqueex-separator-date">' . esc_html(current_time('F j, Y')) . '</span>';
840 break;
841
842 case 'featured_image':
843 // For custom content, we can show a default image or skip
844 // Since there's no post context, we'll show a placeholder or skip
845 break;
846 }
847
848 echo '</span>';
849 }
850
851 /**
852 * Render widget output on the frontend
853 *
854 * This method handles the complete rendering of the news ticker widget,
855 * including label positioning, content generation, and marquee setup.
856 */
857 protected function render() {
858 $settings = $this->get_settings_for_display();
859 // Extract and prepare widget configuration
860 $widget_config = $this->prepare_widget_config($settings);
861
862 $this->add_render_attribute(
863 [
864 'marqueex-marquee-block' => [
865 'class' => [
866 'marqueex-marquee-block',
867 'marqueex-image-marquee',
868 $settings['pause_on_hover'] === 'yes' ? 'is-paused' : '',
869 $settings['reverse_direction'] === 'yes' ? 'is-reversed' : '',
870 $settings['direction_vertical'] === 'yes' ? 'marqueex-marquee-vertical' : 'marqueex-marquee-horizontal',
871 ],
872 'data-speed' => max(10, min(100, intval($settings['speed']['size'] ?? 30))),
873 // 'data-direction' => $settings['reverse_direction'] === 'yes' ? 'right' : 'left',
874 // 'data-pause-on-hover' => $settings['pause_on_hover'] === 'yes' ? 'true' : 'false',
875 'role' => 'region',
876 'aria-label' => __('Image Marquee', 'marqueex'),
877 ]
878 ]
879 );
880
881 // Render the track content once and reuse it for the aria-hidden clone,
882 // avoiding a second identical post query per widget render.
883 ob_start();
884 $this->render_ticker_content_by_type($settings, $widget_config);
885 $track_html = ob_get_clean();
886
887 ?>
888 <?php // get_render_attribute_string() returns markup already escaped by Elementor. ?>
889 <div <?php echo $this->get_render_attribute_string('marqueex-marquee-block'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
890 <div class="marqueex-marquee-track-wrapper">
891 <div class="marqueex-marquee-track">
892 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
893 </div>
894 <div aria-hidden="true" class="marqueex-marquee-track">
895 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
896 </div>
897 </div>
898 </div>
899
900 <?php
901 }
902 }
903