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

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

1,748 lines 58.6 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 Wpxero\Marqueex\Traits\MarqueeSource;
14
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 /**
20 * MarqueeX Text Marquee Widget for Elementor
21 */
22 class TextMarquee extends Widget_Base {
23
24 use MarqueeSource;
25
26 /**
27 * Get widget name
28 */
29 public function get_name() {
30 return 'marqueex-text-marquee';
31 }
32
33 /**
34 * Get widget title
35 */
36 public function get_title() {
37 return __('Text Marquee', 'marqueex');
38 }
39
40 /**
41 * Get widget icon
42 */
43 public function get_icon() {
44 return 'eicon-text-area eicon-marqueex';
45 }
46
47 /**
48 * Get widget categories
49 */
50 public function get_categories() {
51 return ['marqueex'];
52 }
53
54 /**
55 * Enqueue widget scripts and styles
56 */
57 public function get_script_depends() {
58 return ['marqueex-text-marquee'];
59 }
60
61 public function get_style_depends() {
62 return ['marqueex-text-marquee'];
63 }
64 /**
65 * Get widget keywords
66 */
67 public function get_keywords() {
68 return ['marquee', 'ticker', 'marquee', 'posts', 'slider', 'scroll'];
69 }
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' => __('Content Type', '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' => 'posts',
91 'options' => [
92 'posts' => __('Dynamic Posts', 'marqueex'),
93 'custom' => __('Custom Text', 'marqueex'),
94 ],
95 ]
96 );
97
98 $this->add_control(
99 'ticker_content_custom',
100 [
101 'label' => __('Ticker Content', 'marqueex'),
102 'type' => Controls_Manager::REPEATER,
103 'condition' => [
104 'content_type' => 'custom',
105 ],
106 'fields' => [
107 [
108 'name' => 'icon',
109 'label' => __('Icon', 'marqueex'),
110 'type' => Controls_Manager::ICONS,
111 'label_block' => true,
112 'skin' => 'inline',
113 'default' => [
114 'value' => '',
115 'library' => '',
116 ],
117 'description' => __('Choose an icon to display before the text (optional).', 'marqueex'),
118 ],
119 [
120 'name' => 'text',
121 'label' => __('Text', 'marqueex'),
122 'type' => Controls_Manager::TEXT,
123 'default' => __('Breaking news: Your news content here...', 'marqueex'),
124 'description' => __('Enter the news content that will scroll in the ticker.', '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 news content.', 'marqueex'),
136 ],
137 ],
138 'condition' => [
139 'content_type' => 'custom',
140 ],
141 'title_field' => '{{{ text }}}',
142 'default' => [
143 [
144 'text' => __('Breaking news: Your first news content here...', 'marqueex'),
145 'icon' => [
146 'value' => 'fas fa-circle',
147 'library' => 'fa-solid',
148 ],
149 'link' => [
150 'url' => '',
151 'is_external' => '',
152 'nofollow' => '',
153 ],
154 ],
155 [
156 'text' => __('Latest update: Your second news content here...', 'marqueex'),
157 'icon' => [
158 'value' => 'fas fa-circle',
159 'library' => 'fa-solid',
160 ],
161 'link' => [
162 'url' => '',
163 'is_external' => '',
164 'nofollow' => '',
165 ],
166 ],
167 [
168 'text' => __('Important announcement: Your third news content here...', 'marqueex'),
169 'icon' => [
170 'value' => 'fas fa-circle',
171 'library' => 'fa-solid',
172 ],
173 'link' => [
174 'url' => '',
175 'is_external' => '',
176 'nofollow' => '',
177 ],
178 ],
179 ],
180 ]
181 );
182
183 $this->add_responsive_control(
184 'item_spacing',
185 [
186 'label' => __('Item Spacing', 'marqueex'),
187 'type' => Controls_Manager::SLIDER,
188 'size_units' => ['px', 'em', 'rem'],
189 'range' => [
190 'px' => [
191 'min' => 0,
192 'max' => 100,
193 'step' => 1,
194 ],
195 'em' => [
196 'min' => 0,
197 'max' => 10,
198 'step' => 0.1,
199 ],
200 'rem' => [
201 'min' => 0,
202 'max' => 10,
203 'step' => 0.1,
204 ],
205 ],
206 'default' => [
207 'unit' => 'rem',
208 'size' => 2,
209 ],
210 'selectors' => [
211 '{{WRAPPER}} .marqueex-marquee-track-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
212 '{{WRAPPER}} .marqueex-marquee-track' => 'gap: {{SIZE}}{{UNIT}};',
213 '{{WRAPPER}} .marqueex-marquee-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
214 ],
215 'separator' => 'before',
216 ]
217 );
218 $this->end_controls_section();
219
220 // Query Section
221 $this->start_controls_section(
222 'query_section',
223 [
224 'label' => __('Query', 'marqueex'),
225 'tab' => Controls_Manager::TAB_CONTENT,
226 'condition' => [
227 'content_type' => 'posts',
228 ],
229 ]
230 );
231
232 $this->add_control(
233 'post_type',
234 [
235 'label' => __('Post Type', 'marqueex'),
236 'type' => Controls_Manager::SELECT,
237 'default' => 'post',
238 'options' => $this->get_post_types(),
239 ]
240 );
241
242 $this->add_control(
243 'posts_per_page',
244 [
245 'label' => __('Number of Posts', 'marqueex'),
246 'type' => Controls_Manager::NUMBER,
247 'default' => 6,
248 'min' => 1,
249 'max' => 50,
250 ]
251 );
252
253 $this->add_control(
254 'orderby',
255 [
256 'label' => __('Order By', 'marqueex'),
257 'type' => Controls_Manager::SELECT,
258 'default' => 'date',
259 'options' => [
260 'date' => __('Date', 'marqueex'),
261 'title' => __('Title', 'marqueex'),
262 'rand' => __('Random', 'marqueex'),
263 'menu_order' => __('Menu Order', 'marqueex'),
264 ],
265 ]
266 );
267
268 $this->add_control(
269 'order',
270 [
271 'label' => __('Order', 'marqueex'),
272 'type' => Controls_Manager::SELECT,
273 'default' => 'desc',
274 'options' => [
275 'asc' => __('ASC', 'marqueex'),
276 'desc' => __('DESC', 'marqueex'),
277 ],
278 'condition' => [
279 'orderby!' => 'rand',
280 ],
281 ]
282 );
283
284 $this->add_control(
285 'category_ids',
286 [
287 'label' => __('Categories', 'marqueex'),
288 'type' => Controls_Manager::SELECT2,
289 'multiple' => true,
290 'options' => $this->get_post_categories(),
291 'condition' => [
292 'post_type' => 'post',
293 ],
294 ]
295 );
296
297 $this->end_controls_section();
298
299
300 // Content Options Section
301 $this->start_controls_section(
302 'content_options_section',
303 [
304 'label' => __('Content Options', 'marqueex'),
305 'tab' => Controls_Manager::TAB_CONTENT,
306 'condition' => [
307 'content_type' => 'posts',
308 ],
309 ]
310 );
311
312 $this->add_control(
313 'title_trim_enable',
314 [
315 'label' => __('Trim Title', 'marqueex'),
316 'type' => Controls_Manager::SWITCHER,
317 'label_on' => __('Yes', 'marqueex'),
318 'label_off' => __('No', 'marqueex'),
319 'return_value' => 'yes',
320 'default' => 'no',
321 ]
322 );
323
324 $this->add_control(
325 'title_trim_length',
326 [
327 'label' => __('Title Length', 'marqueex'),
328 'type' => Controls_Manager::NUMBER,
329 'default' => 50,
330 'min' => 1,
331 'max' => 200,
332 'condition' => [
333 'title_trim_enable' => 'yes',
334 ],
335 ]
336 );
337
338 $this->add_control(
339 'open_in_new_tab',
340 [
341 'label' => __('Open in New Tab', 'marqueex'),
342 'type' => Controls_Manager::SWITCHER,
343 'label_on' => __('Yes', 'marqueex'),
344 'label_off' => __('No', 'marqueex'),
345 'return_value' => 'yes',
346 'default' => 'no',
347 ]
348 );
349
350 $this->end_controls_section();
351
352 // Separator Section
353 $this->start_controls_section(
354 'separator_section',
355 [
356 'label' => __('Separator', 'marqueex'),
357 'tab' => Controls_Manager::TAB_CONTENT,
358 'condition' => [
359 'content_type' => 'posts',
360 ],
361 ]
362 );
363
364 $this->add_control(
365 'separator_type',
366 [
367 'label' => __('Separator Type', 'marqueex'),
368 'type' => Controls_Manager::SELECT,
369 'default' => 'text',
370 'options' => [
371 'none' => __('None', 'marqueex'),
372 'text' => __('Text', 'marqueex'),
373 'icon' => __('Icon', 'marqueex'),
374 // 'date' => __('Date', 'marqueex'),
375 // 'featured_image' => __('Featured Image', 'marqueex'),
376 ],
377 ]
378 );
379
380 // $this->add_control(
381 // 'separator_style_preset',
382 // [
383 // 'label' => __('Separator Style Preset', 'marqueex'),
384 // 'type' => Controls_Manager::SELECT,
385 // 'default' => 'modern',
386 // 'options' => [
387 // 'modern' => __('Modern Glass', 'marqueex'),
388 // 'minimal' => __('Minimal Clean', 'marqueex'),
389 // 'bold' => __('Bold Accent', 'marqueex'),
390 // 'neon' => __('Neon Glow', 'marqueex'),
391 // 'classic' => __('Classic Simple', 'marqueex'),
392 // 'gradient' => __('Gradient Flow', 'marqueex'),
393 // ],
394 // 'condition' => [
395 // 'separator_type!' => 'none',
396 // ],
397 // ]
398 // );
399
400 $this->add_control(
401 'separator_icon',
402 [
403 'label' => __('Separator Icon', 'marqueex'),
404 'type' => Controls_Manager::ICONS,
405 'default' => [
406 'value' => 'fas fa-circle',
407 'library' => 'fa-solid',
408 ],
409 'condition' => [
410 'separator_type' => 'icon',
411 ],
412 ]
413 );
414
415 $this->add_control(
416 'separator_text',
417 [
418 'label' => __('Separator Text', 'marqueex'),
419 'type' => Controls_Manager::TEXT,
420 'default' => '',
421 'condition' => [
422 'separator_type' => 'text',
423 ],
424 ]
425 );
426
427 $this->end_controls_section();
428
429 // Marquee Settings Section
430 $this->start_controls_section(
431 'marquee_settings_section',
432 [
433 'label' => __('Marquee Settings', 'marqueex'),
434 'tab' => Controls_Manager::TAB_CONTENT,
435 ]
436 );
437
438 $this->add_control(
439 'speed',
440 [
441 'label' => __('Speed', 'marqueex'),
442 'type' => Controls_Manager::SLIDER,
443 'size_units' => ['px/s'],
444 'range' => [
445 'px' => [
446 'min' => 10,
447 'max' => 100,
448 'step' => 1,
449 ],
450 ],
451 'default' => [
452 'unit' => 'px',
453 'size' => 10,
454 ],
455 ]
456 );
457 $this->add_control(
458 'direction_vertical',
459 [
460 'label' => __('Direction Vertical', 'marqueex'),
461 'type' => Controls_Manager::SWITCHER,
462 'default' => 'no',
463 'options' => [
464 'no' => __('Horizontal', 'marqueex'),
465 'yes' => __('Vertical', 'marqueex'),
466 ],
467 ]
468 );
469 $this->add_control(
470 'pause_on_hover',
471 [
472 'label' => __('Pause on Hover', 'marqueex'),
473 'type' => Controls_Manager::SWITCHER,
474 'label_on' => __('Yes', 'marqueex'),
475 'label_off' => __('No', 'marqueex'),
476 'return_value' => 'yes',
477 'default' => 'yes',
478 ]
479 );
480
481 $this->add_control(
482 'reverse_direction',
483 [
484 'label' => __('Reverse Direction', 'marqueex'),
485 'type' => Controls_Manager::SWITCHER,
486 'label_on' => __('Yes', 'marqueex'),
487 'label_off' => __('No', 'marqueex'),
488 'return_value' => 'yes',
489 'default' => 'no',
490 ]
491 );
492
493 $this->end_controls_section();
494
495 // Container Style Section
496 $this->start_controls_section(
497 'container_style_section',
498 [
499 'label' => __('Container', 'marqueex'),
500 'tab' => Controls_Manager::TAB_STYLE,
501 ]
502 );
503
504 $this->add_group_control(
505 Group_Control_Background::get_type(),
506 [
507 'name' => 'container_background',
508 'label' => __('Background', 'marqueex'),
509 'types' => ['classic', 'gradient'],
510 'selector' => '{{WRAPPER}} .marqueex-marquee-block',
511 ]
512 );
513
514 $this->add_group_control(
515 Group_Control_Border::get_type(),
516 [
517 'name' => 'container_border',
518 'label' => __('Border', 'marqueex'),
519 'selector' => '{{WRAPPER}} .marqueex-marquee-block',
520 ]
521 );
522
523 $this->add_responsive_control(
524 'container_border_radius',
525 [
526 'label' => __('Border Radius', 'marqueex'),
527 'type' => Controls_Manager::DIMENSIONS,
528 'size_units' => ['px', '%', 'em'],
529 'selectors' => [
530 '{{WRAPPER}} .marqueex-marquee-block' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
531 ],
532 ]
533 );
534
535 $this->add_group_control(
536 Group_Control_Box_Shadow::get_type(),
537 [
538 'name' => 'container_box_shadow',
539 'label' => __('Box Shadow', 'marqueex'),
540 'selector' => '{{WRAPPER}} .marqueex-marquee-block',
541 ]
542 );
543
544 $this->add_responsive_control(
545 'container_padding',
546 [
547 'label' => __('Padding', 'marqueex'),
548 'type' => Controls_Manager::DIMENSIONS,
549 'size_units' => ['px', 'em', '%'],
550 'selectors' => [
551 '{{WRAPPER}} .marqueex-marquee-block' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
552 ],
553 ]
554 );
555
556 $this->add_responsive_control(
557 'container_margin',
558 [
559 'label' => __('Margin', 'marqueex'),
560 'type' => Controls_Manager::DIMENSIONS,
561 'size_units' => ['px', 'em', '%'],
562 'selectors' => [
563 '{{WRAPPER}} .marqueex-marquee-block' => 'margin: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
564 ],
565 ]
566 );
567
568 $this->add_responsive_control(
569 'container_height',
570 [
571 'label' => __('Height', 'marqueex'),
572 'type' => Controls_Manager::SLIDER,
573 'size_units' => ['px', 'vh', 'em'],
574 'range' => [
575 'px' => [
576 'min' => 30,
577 'max' => 200,
578 ],
579 'vh' => [
580 'min' => 1,
581 'max' => 20,
582 ],
583 ],
584 'selectors' => [
585 '{{WRAPPER}} .marqueex-marquee-block' => 'height: {{SIZE}}{{UNIT}};',
586 ],
587 ]
588 );
589
590 $this->add_control(
591 'container_overflow',
592 [
593 'label' => __('Overflow', 'marqueex'),
594 'type' => Controls_Manager::SELECT,
595 'default' => 'hidden',
596 'options' => [
597 'visible' => __('Visible', 'marqueex'),
598 'hidden' => __('Hidden', 'marqueex'),
599 'scroll' => __('Scroll', 'marqueex'),
600 'auto' => __('Auto', 'marqueex'),
601 ],
602 'selectors' => [
603 '{{WRAPPER}} .marqueex-marquee-block' => 'overflow: {{VALUE}};',
604 ],
605 ]
606 );
607
608 $this->end_controls_section();
609
610 // Text Style Section
611 $this->start_controls_section(
612 'text_style_section',
613 [
614 'label' => __('Text', 'marqueex'),
615 'tab' => Controls_Manager::TAB_STYLE,
616 ]
617 );
618 $this->start_controls_tabs(
619 'style_tabs'
620 );
621
622 $this->start_controls_tab(
623 'style_normal_tab',
624 [
625 'label' => esc_html__('Normal', 'marqueex'),
626 ]
627 );
628 $this->add_control(
629 'text_color',
630 [
631 'label' => __('Text Color', 'marqueex'),
632 'type' => Controls_Manager::COLOR,
633 'selectors' => [
634 '{{WRAPPER}} .marqueex-news-text' => 'color: {{VALUE}};',
635 '{{WRAPPER}} .marqueex-title-link' => 'color: {{VALUE}};',
636 ],
637 ]
638 );
639
640 $this->add_group_control(
641 Group_Control_Typography::get_type(),
642 [
643 'name' => 'text_typography',
644 'selector' => '{{WRAPPER}} .marqueex-news-text, {{WRAPPER}} .marqueex-title-link',
645 ]
646 );
647
648 $this->add_group_control(
649 Group_Control_Text_Shadow::get_type(),
650 [
651 'name' => 'text_shadow',
652 'selector' => '{{WRAPPER}} .marqueex-news-text, {{WRAPPER}} .marqueex-title-link',
653 ]
654 );
655
656 $this->add_responsive_control(
657 'text_spacing',
658 [
659 'label' => __('Letter Spacing', 'marqueex'),
660 'type' => Controls_Manager::SLIDER,
661 'size_units' => ['px', 'em'],
662 'range' => [
663 'px' => [
664 'min' => -2,
665 'max' => 10,
666 'step' => 0.1,
667 ],
668 ],
669 'selectors' => [
670 '{{WRAPPER}} .marqueex-news-text' => 'letter-spacing: {{SIZE}}{{UNIT}};',
671 ],
672 ]
673 );
674
675 $this->add_responsive_control(
676 'word_spacing',
677 [
678 'label' => __('Word Spacing', 'marqueex'),
679 'type' => Controls_Manager::SLIDER,
680 'size_units' => ['px', 'em'],
681 'range' => [
682 'px' => [
683 'min' => 0,
684 'max' => 50,
685 ],
686 ],
687 'selectors' => [
688 '{{WRAPPER}} .marqueex-news-text' => 'word-spacing: {{SIZE}}{{UNIT}};',
689 ],
690 ]
691 );
692
693 $this->end_controls_tab();
694 $this->start_controls_tab(
695 'link_normal_tab',
696 [
697 'label' => __('Link', 'marqueex'),
698 ]
699 );
700
701 $this->add_control(
702 'link_color',
703 [
704 'label' => __('Link Color', 'marqueex'),
705 'type' => Controls_Manager::COLOR,
706 'selectors' => [
707 '{{WRAPPER}} .marqueex-title-link' => 'color: {{VALUE}};',
708 ],
709 ]
710 );
711
712 $this->add_control(
713 'link_decoration',
714 [
715 'label' => __('Text Decoration', 'marqueex'),
716 'type' => Controls_Manager::SELECT,
717 'default' => 'none',
718 'options' => [
719 'none' => __('None', 'marqueex'),
720 'underline' => __('Underline', 'marqueex'),
721 'overline' => __('Overline', 'marqueex'),
722 'line-through' => __('Line Through', 'marqueex'),
723 ],
724 'selectors' => [
725 '{{WRAPPER}} .marqueex-title-link' => 'text-decoration: {{VALUE}};',
726 ],
727 ]
728 );
729 $this->add_control(
730 'link_hover_heading',
731 [
732 'label' => __('H O V E R', 'marqueex'),
733 'type' => Controls_Manager::HEADING,
734 'separator' => 'before',
735 ]
736 );
737 $this->add_control(
738 'link_hover_color',
739 [
740 'label' => __('Color', 'marqueex'),
741 'type' => Controls_Manager::COLOR,
742 'selectors' => [
743 '{{WRAPPER}} .marqueex-title-link:hover' => 'color: {{VALUE}};',
744 ],
745 ]
746 );
747
748 $this->add_control(
749 'link_hover_decoration',
750 [
751 'label' => __('Text Decoration', 'marqueex'),
752 'type' => Controls_Manager::SELECT,
753 'default' => 'underline',
754 'options' => [
755 'none' => __('None', 'marqueex'),
756 'underline' => __('Underline', 'marqueex'),
757 'overline' => __('Overline', 'marqueex'),
758 'line-through' => __('Line Through', 'marqueex'),
759 ],
760 'selectors' => [
761 '{{WRAPPER}} .marqueex-title-link:hover' => 'text-decoration: {{VALUE}};',
762 ],
763 ]
764 );
765
766 $this->add_control(
767 'link_hover_transition',
768 [
769 'label' => __('Transition Duration', 'marqueex'),
770 'type' => Controls_Manager::SLIDER,
771 'size_units' => ['ms', 's'],
772 'range' => [
773 'ms' => [
774 'min' => 100,
775 'max' => 1000,
776 'step' => 50,
777 ],
778 's' => [
779 'min' => 0.1,
780 'max' => 2,
781 'step' => 0.1,
782 ],
783 ],
784 'default' => [
785 'unit' => 'ms',
786 'size' => 300,
787 ],
788 'selectors' => [
789 '{{WRAPPER}} .marqueex-title-link' => 'transition: all {{SIZE}}{{UNIT}} ease;',
790 ],
791 ]
792 );
793 $this->end_controls_tab();
794
795 $this->end_controls_tabs();
796
797 $this->end_controls_section();
798
799 // // Link Style Section
800 // $this->start_controls_section(
801 // 'link_style_section',
802 // [
803 // 'label' => __('Link', 'marqueex'),
804 // 'tab' => Controls_Manager::TAB_STYLE,
805 // ]
806 // );
807
808 // $this->start_controls_tabs('link_style_tabs');
809
810
811
812 // $this->end_controls_tab();
813
814 // $this->start_controls_tab(
815 // 'link_hover_tab',
816 // [
817 // 'label' => __('Hover', 'marqueex'),
818 // ]
819 // );
820
821
822
823 // $this->end_controls_tab();
824
825 // $this->end_controls_tabs();
826
827 // $this->end_controls_section();
828
829 // Separator Style Section
830 $this->start_controls_section(
831 'separator_style_section',
832 [
833 'label' => __('Separator', 'marqueex'),
834 'tab' => Controls_Manager::TAB_STYLE,
835 'conditions' => [
836 'relation' => 'or',
837 'terms' => [
838 [
839 'relation' => 'and',
840 'terms' => [
841 [
842 'name' => 'content_type',
843 'operator' => '===',
844 'value' => 'posts',
845 ],
846 [
847 'name' => 'separator_type',
848 'operator' => '!==',
849 'value' => 'none',
850 ],
851 ],
852 ],
853 [
854 'relation' => 'and',
855 'terms' => [
856 [
857 'name' => 'content_type',
858 'operator' => '===',
859 'value' => 'custom',
860 ],
861 ],
862 ],
863 ],
864 ],
865 ]
866 );
867
868 $this->add_control(
869 'separator_color',
870 [
871 'label' => __('Separator Color', 'marqueex'),
872 'type' => Controls_Manager::COLOR,
873 'default' => '#333',
874 'selectors' => [
875 '{{WRAPPER}} .marqueex-separator' => 'color: {{VALUE}};',
876 '{{WRAPPER}} .marqueex-separator svg' => 'fill: {{VALUE}};',
877 ],
878 ]
879 );
880
881 $this->add_group_control(
882 Group_Control_Typography::get_type(),
883 [
884 'name' => 'separator_typography',
885 'selector' => '{{WRAPPER}} .marqueex-separator',
886 'condition' => [
887 'separator_type' => ['text', 'date'],
888 ],
889 ]
890 );
891
892 $this->add_responsive_control(
893 'separator_size',
894 [
895 'label' => __('Icon Size', 'marqueex'),
896 'type' => Controls_Manager::SLIDER,
897 'size_units' => ['px', 'em'],
898 'range' => [
899 'px' => [
900 'min' => 8,
901 'max' => 50,
902 ],
903 ],
904 'selectors' => [
905 '{{WRAPPER}} .marqueex-separator-icon' => 'font-size: {{SIZE}}{{UNIT}};',
906 '{{WRAPPER}} .marqueex-separator-icon svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
907 ],
908 'condition' => [
909 'separator_type' => 'icon',
910 ],
911 ]
912 );
913 $this->add_responsive_control(
914 'icon_size',
915 [
916 'label' => __('Icon Size', 'marqueex'),
917 'type' => Controls_Manager::SLIDER,
918 'size_units' => ['px', 'em', 'rem'],
919 'range' => [
920 'px' => [
921 'min' => 8,
922 'max' => 100,
923 'step' => 1,
924 ],
925 'em' => [
926 'min' => 0.5,
927 'max' => 6,
928 'step' => 0.1,
929 ],
930 'rem' => [
931 'min' => 0.5,
932 'max' => 6,
933 'step' => 0.1,
934 ],
935 ],
936 'default' => [
937 'unit' => 'px',
938 'size' => 16,
939 ],
940 'selectors' => [
941 '{{WRAPPER}} .marqueex-separator' => 'font-size: {{SIZE}}{{UNIT}};',
942 '{{WRAPPER}} .marqueex-separator svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
943 ],
944 ]
945 );
946
947 $this->add_responsive_control(
948 'icon_spacing',
949 [
950 'label' => __('Icon Spacing', 'marqueex'),
951 'type' => Controls_Manager::SLIDER,
952 'size_units' => ['px', 'em', 'rem'],
953 'range' => [
954 'px' => [
955 'min' => 0,
956 'max' => 50,
957 'step' => 1,
958 ],
959 'em' => [
960 'min' => 0,
961 'max' => 3,
962 'step' => 0.1,
963 ],
964 'rem' => [
965 'min' => 0,
966 'max' => 3,
967 'step' => 0.1,
968 ],
969 ],
970 'default' => [
971 'unit' => 'px',
972 'size' => 8,
973 ],
974 'selectors' => [
975 '{{WRAPPER}} .marqueex-separator' => 'margin-right: {{SIZE}}{{UNIT}};',
976 ],
977 ]
978 );
979 // $this->add_responsive_control(
980 // 'separator_spacing',
981 // [
982 // 'label' => __('Separator Spacing', 'marqueex'),
983 // 'type' => Controls_Manager::SLIDER,
984 // 'size_units' => ['px', 'em'],
985 // 'range' => [
986 // 'px' => [
987 // 'min' => 0,
988 // 'max' => 100,
989 // ],
990 // ],
991 // 'selectors' => [
992 // '{{WRAPPER}} .marqueex-separator' => 'margin: 0 {{SIZE}}{{UNIT}};',
993 // ],
994 // ]
995 // );
996
997 $this->add_group_control(
998 Group_Control_Background::get_type(),
999 [
1000 'name' => 'separator_background',
1001 'label' => __('Background', 'marqueex'),
1002 'types' => ['classic', 'gradient'],
1003 'selector' => '{{WRAPPER}} .marqueex-separator',
1004 'fields_options' => [
1005 'background' => [
1006 'default' => 'classic',
1007 ],
1008 ],
1009 ]
1010 );
1011
1012 $this->add_group_control(
1013 Group_Control_Border::get_type(),
1014 [
1015 'name' => 'separator_border',
1016 'label' => __('Border', 'marqueex'),
1017 'selector' => '{{WRAPPER}} .marqueex-separator',
1018 ]
1019 );
1020
1021 $this->add_responsive_control(
1022 'separator_border_radius',
1023 [
1024 'label' => __('Border Radius', 'marqueex'),
1025 'type' => Controls_Manager::DIMENSIONS,
1026 'size_units' => ['px', '%', 'em'],
1027 'selectors' => [
1028 '{{WRAPPER}} .marqueex-separator' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1029 ],
1030 ]
1031 );
1032
1033 $this->add_group_control(
1034 Group_Control_Box_Shadow::get_type(),
1035 [
1036 'name' => 'separator_box_shadow',
1037 'label' => __('Box Shadow', 'marqueex'),
1038 'selector' => '{{WRAPPER}} .marqueex-separator',
1039 ]
1040 );
1041
1042 $this->add_group_control(
1043 Group_Control_Text_Shadow::get_type(),
1044 [
1045 'name' => 'separator_text_shadow',
1046 'label' => __('Text Shadow', 'marqueex'),
1047 'selector' => '{{WRAPPER}} .marqueex-separator',
1048 'condition' => [
1049 'separator_type' => ['text', 'date'],
1050 ],
1051 ]
1052 );
1053
1054 $this->add_responsive_control(
1055 'separator_padding',
1056 [
1057 'label' => __('Padding', 'marqueex'),
1058 'type' => Controls_Manager::DIMENSIONS,
1059 'size_units' => ['px', '%', 'em'],
1060 'selectors' => [
1061 '{{WRAPPER}} .marqueex-separator' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1062 ],
1063 ]
1064 );
1065
1066 $this->add_responsive_control(
1067 'separator_opacity',
1068 [
1069 'label' => __('Opacity', 'marqueex'),
1070 'type' => Controls_Manager::SLIDER,
1071 'range' => [
1072 'px' => [
1073 'max' => 1,
1074 'min' => 0.10,
1075 'step' => 0.01,
1076 ],
1077 ],
1078 'selectors' => [
1079 '{{WRAPPER}} .marqueex-separator' => 'opacity: {{SIZE}};',
1080 ],
1081 ]
1082 );
1083
1084 $this->add_control(
1085 'separator_backdrop_filter',
1086 [
1087 'label' => __('Backdrop Filter Blur', 'marqueex'),
1088 'type' => Controls_Manager::SLIDER,
1089 'size_units' => ['px'],
1090 'range' => [
1091 'px' => [
1092 'min' => 0,
1093 'max' => 20,
1094 ],
1095 ],
1096 'selectors' => [
1097 '{{WRAPPER}} .marqueex-separator' => 'backdrop-filter: blur({{SIZE}}{{UNIT}});',
1098 ],
1099 ]
1100 );
1101
1102 $this->add_control(
1103 'separator_transform',
1104 [
1105 'label' => __('Transform', 'marqueex'),
1106 'type' => Controls_Manager::TEXT,
1107 'placeholder' => __('scale(1.1) rotate(5deg)', 'marqueex'),
1108 'selectors' => [
1109 '{{WRAPPER}} .marqueex-separator' => 'transform: {{VALUE}};',
1110 ],
1111 ]
1112 );
1113
1114 // Separator Hover Effects
1115 $this->add_control(
1116 'separator_hover_heading',
1117 [
1118 'label' => __('Hover Effects', 'marqueex'),
1119 'type' => Controls_Manager::HEADING,
1120 'separator' => 'before',
1121 ]
1122 );
1123
1124 $this->add_control(
1125 'separator_hover_color',
1126 [
1127 'label' => __('Hover Color', 'marqueex'),
1128 'type' => Controls_Manager::COLOR,
1129 'selectors' => [
1130 '{{WRAPPER}} .marqueex-separator:hover' => 'color: {{VALUE}};',
1131 '{{WRAPPER}} .marqueex-separator:hover svg' => 'fill: {{VALUE}};',
1132 ],
1133 ]
1134 );
1135
1136 $this->add_group_control(
1137 Group_Control_Background::get_type(),
1138 [
1139 'name' => 'separator_hover_background',
1140 'label' => __('Hover Background', 'marqueex'),
1141 'types' => ['classic', 'gradient'],
1142 'selector' => '{{WRAPPER}} .marqueex-separator:hover',
1143 ]
1144 );
1145
1146 $this->add_group_control(
1147 Group_Control_Border::get_type(),
1148 [
1149 'name' => 'separator_hover_border',
1150 'label' => __('Hover Border', 'marqueex'),
1151 'selector' => '{{WRAPPER}} .marqueex-separator:hover',
1152 ]
1153 );
1154
1155 $this->add_group_control(
1156 Group_Control_Box_Shadow::get_type(),
1157 [
1158 'name' => 'separator_hover_box_shadow',
1159 'label' => __('Hover Box Shadow', 'marqueex'),
1160 'selector' => '{{WRAPPER}} .marqueex-separator:hover',
1161 ]
1162 );
1163
1164 $this->add_control(
1165 'separator_hover_transform',
1166 [
1167 'label' => __('Hover Transform', 'marqueex'),
1168 'type' => Controls_Manager::TEXT,
1169 'placeholder' => __('translateY(-2px) scale(1.05)', 'marqueex'),
1170 'selectors' => [
1171 '{{WRAPPER}} .marqueex-separator:hover' => 'transform: {{VALUE}};',
1172 ],
1173 ]
1174 );
1175
1176 $this->add_responsive_control(
1177 'separator_hover_opacity',
1178 [
1179 'label' => __('Hover Opacity', 'marqueex'),
1180 'type' => Controls_Manager::SLIDER,
1181 'range' => [
1182 'px' => [
1183 'max' => 1,
1184 'min' => 0.10,
1185 'step' => 0.01,
1186 ],
1187 ],
1188 'selectors' => [
1189 '{{WRAPPER}} .marqueex-separator:hover' => 'opacity: {{SIZE}};',
1190 ],
1191 ]
1192 );
1193
1194 $this->add_control(
1195 'separator_transition_duration',
1196 [
1197 'label' => __('Transition Duration', 'marqueex'),
1198 'type' => Controls_Manager::SLIDER,
1199 'size_units' => ['ms'],
1200 'range' => [
1201 'ms' => [
1202 'min' => 100,
1203 'max' => 2000,
1204 'step' => 50,
1205 ],
1206 ],
1207 'default' => [
1208 'size' => 300,
1209 'unit' => 'ms',
1210 ],
1211 'selectors' => [
1212 '{{WRAPPER}} .marqueex-separator' => 'transition: all {{SIZE}}{{UNIT}} ease;',
1213 ],
1214 ]
1215 );
1216
1217 $this->add_responsive_control(
1218 'separator_image_size',
1219 [
1220 'label' => __('Image Size', 'marqueex'),
1221 'type' => Controls_Manager::SLIDER,
1222 'size_units' => ['px'],
1223 'range' => [
1224 'px' => [
1225 'min' => 20,
1226 'max' => 100,
1227 ],
1228 ],
1229 'selectors' => [
1230 '{{WRAPPER}} .marqueex-separator-feature-image img' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}}; object-fit: cover;',
1231 ],
1232 'condition' => [
1233 'separator_type' => 'featured_image',
1234 ],
1235 ]
1236 );
1237
1238 $this->add_responsive_control(
1239 'separator_image_border_radius',
1240 [
1241 'label' => __('Image Border Radius', 'marqueex'),
1242 'type' => Controls_Manager::DIMENSIONS,
1243 'size_units' => ['px', '%'],
1244 'selectors' => [
1245 '{{WRAPPER}} .marqueex-separator-feature-image img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
1246 ],
1247 'condition' => [
1248 'separator_type' => 'featured_image',
1249 ],
1250 ]
1251 );
1252
1253 $this->end_controls_section();
1254
1255 // // Icon Style Section
1256 // $this->start_controls_section(
1257 // 'icon_style_section',
1258 // [
1259 // 'label' => __('Icon', 'marqueex'),
1260 // 'tab' => Controls_Manager::TAB_STYLE,
1261 // 'condition' => [
1262 // 'content_type' => 'custom',
1263 // ],
1264 // ]
1265 // );
1266
1267 // $this->add_control(
1268 // 'icon_color',
1269 // [
1270 // 'label' => __('Icon Color', 'marqueex'),
1271 // 'type' => Controls_Manager::COLOR,
1272 // 'default' => '#333',
1273 // 'selectors' => [
1274 // '{{WRAPPER}} .marqueex-news-icon' => 'color: {{VALUE}};',
1275 // '{{WRAPPER}} .marqueex-news-icon svg' => 'fill: {{VALUE}};',
1276 // ],
1277 // ]
1278 // );
1279
1280 // $this->add_responsive_control(
1281 // 'icon_size',
1282 // [
1283 // 'label' => __('Icon Size', 'marqueex'),
1284 // 'type' => Controls_Manager::SLIDER,
1285 // 'size_units' => ['px', 'em', 'rem'],
1286 // 'range' => [
1287 // 'px' => [
1288 // 'min' => 8,
1289 // 'max' => 100,
1290 // 'step' => 1,
1291 // ],
1292 // 'em' => [
1293 // 'min' => 0.5,
1294 // 'max' => 6,
1295 // 'step' => 0.1,
1296 // ],
1297 // 'rem' => [
1298 // 'min' => 0.5,
1299 // 'max' => 6,
1300 // 'step' => 0.1,
1301 // ],
1302 // ],
1303 // 'default' => [
1304 // 'unit' => 'px',
1305 // 'size' => 16,
1306 // ],
1307 // 'selectors' => [
1308 // '{{WRAPPER}} .marqueex-news-icon' => 'font-size: {{SIZE}}{{UNIT}};',
1309 // '{{WRAPPER}} .marqueex-news-icon svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
1310 // ],
1311 // ]
1312 // );
1313
1314 // $this->add_responsive_control(
1315 // 'icon_spacing',
1316 // [
1317 // 'label' => __('Icon Spacing', 'marqueex'),
1318 // 'type' => Controls_Manager::SLIDER,
1319 // 'size_units' => ['px', 'em', 'rem'],
1320 // 'range' => [
1321 // 'px' => [
1322 // 'min' => 0,
1323 // 'max' => 50,
1324 // 'step' => 1,
1325 // ],
1326 // 'em' => [
1327 // 'min' => 0,
1328 // 'max' => 3,
1329 // 'step' => 0.1,
1330 // ],
1331 // 'rem' => [
1332 // 'min' => 0,
1333 // 'max' => 3,
1334 // 'step' => 0.1,
1335 // ],
1336 // ],
1337 // 'default' => [
1338 // 'unit' => 'px',
1339 // 'size' => 8,
1340 // ],
1341 // 'selectors' => [
1342 // '{{WRAPPER}} .marqueex-news-icon' => 'margin-right: {{SIZE}}{{UNIT}};',
1343 // ],
1344 // ]
1345 // );
1346
1347 // $this->add_control(
1348 // 'icon_vertical_align',
1349 // [
1350 // 'label' => __('Vertical Alignment', 'marqueex'),
1351 // 'type' => Controls_Manager::SELECT,
1352 // 'default' => 'middle',
1353 // 'options' => [
1354 // 'top' => __('Top', 'marqueex'),
1355 // 'middle' => __('Middle', 'marqueex'),
1356 // 'bottom' => __('Bottom', 'marqueex'),
1357 // ],
1358 // 'selectors' => [
1359 // '{{WRAPPER}} .marqueex-news-icon' => 'vertical-align: {{VALUE}};',
1360 // ],
1361 // ]
1362 // );
1363
1364 // $this->add_group_control(
1365 // Group_Control_Text_Shadow::get_type(),
1366 // [
1367 // 'name' => 'icon_text_shadow',
1368 // 'selector' => '{{WRAPPER}} .marqueex-news-icon',
1369 // ]
1370 // );
1371
1372 // $this->end_controls_section();
1373 }
1374
1375 /**
1376 * Prepare widget configuration from settings
1377 *
1378 * Extracts and processes all widget settings into a clean configuration array.
1379 *
1380 * @param array $settings Widget settings from Elementor
1381 * @return array Processed configuration array
1382 */
1383 private function prepare_widget_config($settings) {
1384 return [
1385 'unique_id' => 'marqueex-ticker-' . $this->get_id(),
1386 'speed' => max(10, min(100, intval($settings['speed']['size'] ?? 10))), // Security: Clamp speed values
1387 'marquee_direction' => $settings['marquee_direction'] ?? 'horizontal',
1388 'pause_on_hover' => $settings['pause_on_hover'] === 'yes' ? 'true' : 'false',
1389 'reverse_direction' => $settings['reverse_direction'] === 'yes',
1390 'content_type' => in_array($settings['content_type'] ?? 'posts', ['posts', 'custom']) ? $settings['content_type'] : 'posts',
1391 ];
1392 }
1393
1394 /**
1395 * Render ticker content based on content type
1396 *
1397 * @param array $settings Widget settings
1398 * @param array $config Widget configuration
1399 */
1400 private function render_ticker_content_by_type($settings, $config) {
1401 if ($config['content_type'] === 'posts') {
1402 $this->render_dynamic_posts_content($settings);
1403 } else {
1404 $this->render_custom_text_content($settings);
1405 }
1406 }
1407
1408 /**
1409 * Render dynamic posts content
1410 *
1411 * @param array $settings Widget settings
1412 */
1413 private function render_dynamic_posts_content($settings) {
1414 $query_args = $this->get_query_args($settings);
1415 $posts = get_posts($query_args);
1416 $this->render_ticker_content($settings, $posts);
1417 }
1418
1419 /**
1420 * Render custom text content
1421 *
1422 * @param array $settings Widget settings
1423 */
1424 private function render_custom_text_content($settings) {
1425 $ticker_content = $settings['ticker_content_custom'] ?? [];
1426
1427 if (empty($ticker_content)) {
1428 return;
1429 }
1430
1431 echo '<div class="marqueex-marquee-wrapper">';
1432 $total_items = count($ticker_content);
1433 $ticker_content = $this->ensure_minimum_posts($ticker_content);
1434
1435 foreach ($ticker_content as $index => $item) {
1436 $text = $item['text'] ?? '';
1437 $link = $item['link'] ?? [];
1438 $icon = $item['icon'] ?? [];
1439
1440 if (empty($text)) {
1441 continue;
1442 }
1443
1444 echo '<div class="marqueex-news-item">';
1445 // if ($settings['direction_vertical'] === 'yes') {
1446 // $this->render_separator($settings, $item);
1447 // }
1448 $this->render_icons($settings, $icon);
1449
1450 echo '<span class="marqueex-news-text">';
1451
1452 // Render icon if available
1453
1454 if (!empty($link['url'])) {
1455 $rel_attrs = [];
1456
1457 if ($link['is_external']) {
1458 $rel_attrs[] = 'noopener noreferrer';
1459 }
1460 if ($link['nofollow']) {
1461 $rel_attrs[] = 'nofollow';
1462 }
1463
1464 $rel_value = implode(' ', $rel_attrs);
1465
1466 echo '<a href="' . esc_url($link['url']) . '"'
1467 . ($link['is_external'] ? ' target="_blank"' : '')
1468 . ($rel_value ? ' rel="' . esc_attr($rel_value) . '"' : '')
1469 . ' class="marqueex-title-link" aria-label="' . esc_attr($text) . '">'
1470 . esc_html($text) . '</a>';
1471 } else {
1472 echo esc_html($text);
1473 }
1474
1475 echo '</span>';
1476 echo '</div>';
1477
1478 // Only render separator if not the last item
1479 // if ($settings['direction_vertical'] !== 'yes') {
1480 // // Render icon if available
1481 // $this->render_icons($settings, $icon);
1482 // }
1483 }
1484 echo '</div>';
1485 }
1486
1487
1488 /**
1489 * Prepare link configuration
1490 *
1491 * @param array $settings Widget settings
1492 * @return array Link configuration
1493 */
1494 private function prepare_link_config($settings) {
1495 return [
1496 'enable_links' => $settings['enable_links'] ?? 'yes',
1497 'open_in_new_tab' => $settings['open_in_new_tab'] ?? 'no'
1498 ];
1499 }
1500
1501 /**
1502 * Render single news item
1503 *
1504 * @param object $post Post object
1505 * @param array $settings Widget settings
1506 * @param array $link_config Link configuration
1507 */
1508 private function render_single_news_item($post, $settings, $link_config) {
1509 if (!$post instanceof \WP_Post) {
1510 return;
1511 }
1512
1513 $title = get_the_title($post);
1514
1515 // Trim title if enabled. Clamp the word count so adversarial /
1516 // unset settings can't produce surprising output.
1517 if (($settings['title_trim_enable'] ?? '') === 'yes') {
1518 $trim_length = max(1, min(200, intval($settings['title_trim_length'] ?? 50)));
1519 $title = wp_trim_words($title, $trim_length, '...');
1520 }
1521
1522 $url = $this->get_post_url($post);
1523
1524 echo '<span class="marqueex-news-text">';
1525
1526 if ($link_config['enable_links'] === 'yes' && !empty($url)) {
1527 echo '<a href="' . esc_url($url) . '"'
1528 . ($link_config['open_in_new_tab'] === 'yes' ? ' target="_blank" rel="noopener noreferrer"' : '')
1529 . ' class="marqueex-title-link" aria-label="' . esc_attr($title) . '">'
1530 . esc_html($title) . '</a>';
1531 } else {
1532 echo esc_html($title);
1533 }
1534
1535 echo '</span>';
1536 }
1537
1538 /**
1539 * Render news ticker content with posts and separators
1540 *
1541 * Handles the rendering of news items with proper title trimming,
1542 * link generation, and separator insertion between items.
1543 *
1544 * @param array $settings Widget settings
1545 * @param array $posts Array of post objects
1546 */
1547 private function render_ticker_content($settings, $posts = []) {
1548 // Ensure we have posts to display
1549 $posts = $this->ensure_minimum_posts($posts);
1550
1551 // Prepare link configuration
1552 $link_config = $this->prepare_link_config($settings);
1553
1554 echo '<div class="marqueex-marquee-wrapper">';
1555
1556 // Render each post with separators
1557 $total_posts = count($posts);
1558 foreach ($posts as $index => $post) {
1559 echo '<div class="marqueex-news-item">';
1560 if ($settings['direction_vertical'] === 'yes') {
1561 $this->render_separator($settings, $post);
1562 }
1563 $this->render_single_news_item($post, $settings, $link_config);
1564 echo '</div>';
1565
1566 // Only render separator if not the last item
1567 if ($settings['direction_vertical'] !== 'yes') {
1568 if ($index < $total_posts - 1) {
1569 $this->render_separator($settings, $post);
1570 }
1571 }
1572 }
1573
1574 echo '</div>';
1575 }
1576
1577 /**
1578 * Render separator based on type
1579 *
1580 * @param array $settings Widget settings
1581 * @param WP_Post $post Current post object
1582 */
1583 private function render_separator($settings, $post) {
1584 $separator_type = $settings['separator_type'] ?? 'none';
1585
1586 if ($separator_type === 'none') {
1587 return;
1588 }
1589
1590 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
1591 $separator_classes = 'marqueex-separator marqueex-separator-' . esc_attr($separator_type) . ' marqueex-separator-preset-' . esc_attr($separator_preset);
1592
1593 echo '<span class="' . esc_attr($separator_classes) . '">';
1594
1595 switch ($separator_type) {
1596 case 'icon':
1597 $separator_icon = $settings['separator_icon'] ?? [];
1598 if (!empty($separator_icon['value'])) {
1599 echo '<span class="marqueex-separator-icon">';
1600 \Elementor\Icons_Manager::render_icon($separator_icon, ['aria-hidden' => 'true']);
1601 echo '</span>';
1602 }
1603 break;
1604
1605 case 'text':
1606 $separator_text = $settings['separator_text'] ?? '|';
1607 echo '<span class="marqueex-separator-text">' . esc_html($separator_text) . '</span>';
1608 break;
1609
1610 case 'date':
1611 echo '<span class="marqueex-separator-date">' . esc_html(get_the_date('', $post)) . '</span>';
1612 break;
1613
1614 case 'featured_image':
1615 if (has_post_thumbnail($post)) {
1616 echo '<span class="marqueex-separator-feature-image">';
1617 echo get_the_post_thumbnail($post, 'thumbnail');
1618 echo '</span>';
1619 }
1620 break;
1621 }
1622
1623 echo '</span>';
1624 }
1625 private function render_icons($settings, $icon) {
1626 $separator_type = $settings['separator_type'] ?? 'icon';
1627
1628 if ($separator_type === 'none') {
1629 return;
1630 }
1631 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
1632 $separator_classes = 'marqueex-separator marqueex-separator-icon marqueex-separator-preset-' . esc_attr($separator_preset);
1633
1634 if (!empty($icon['value'])) {
1635 echo '<span class="' . esc_attr($separator_classes) . '">';
1636 echo '<span class="marqueex-separator-icon">';
1637 \Elementor\Icons_Manager::render_icon($icon, ['aria-hidden' => 'true']);
1638 echo '</span>';
1639 echo '</span>';
1640 }
1641 }
1642
1643 /**
1644 * Render separator for custom content
1645 *
1646 * @param array $settings Widget settings
1647 */
1648 private function render_separator_for_custom($settings) {
1649 $separator_type = $settings['separator_type'] ?? 'none';
1650
1651 if ($separator_type === 'none') {
1652 return;
1653 }
1654
1655 $separator_preset = $settings['separator_style_preset'] ?? 'modern';
1656 $separator_classes = 'marqueex-separator marqueex-separator-' . esc_attr($separator_type) . ' marqueex-separator-preset-' . esc_attr($separator_preset);
1657
1658 echo '<span class="' . esc_attr($separator_classes) . '">';
1659
1660 switch ($separator_type) {
1661 case 'icon':
1662 $separator_icon = $settings['separator_icon'] ?? [];
1663 if (!empty($separator_icon['value'])) {
1664 echo '<span class="marqueex-separator-icon">';
1665 \Elementor\Icons_Manager::render_icon($separator_icon, ['aria-hidden' => 'true']);
1666 echo '</span>';
1667 }
1668 break;
1669
1670 case 'text':
1671 $separator_text = $settings['separator_text'] ?? '|';
1672 echo '<span class="marqueex-separator-text">' . esc_html($separator_text) . '</span>';
1673 break;
1674
1675 case 'date':
1676 echo '<span class="marqueex-separator-date">' . esc_html(current_time('F j, Y')) . '</span>';
1677 break;
1678
1679 case 'featured_image':
1680 // For custom content, we can show a default image or skip
1681 // Since there's no post context, we'll show a placeholder or skip
1682 break;
1683 }
1684
1685 echo '</span>';
1686 }
1687
1688 /**
1689 * Render widget output on the frontend
1690 *
1691 * This method handles the complete rendering of the news ticker widget,
1692 * including label positioning, content generation, and marquee setup.
1693 */
1694 protected function render() {
1695 $settings = $this->get_settings_for_display();
1696 // Extract and prepare widget configuration
1697 $widget_config = $this->prepare_widget_config($settings);
1698
1699 $render_attrs = [
1700 'class' => array_values(array_filter([
1701 'marqueex-marquee-block',
1702 'marqueex-text-marquee',
1703 ($settings['pause_on_hover'] ?? '') === 'yes' ? 'is-paused' : '',
1704 ($settings['reverse_direction'] ?? '') === 'yes' ? 'is-reversed' : '',
1705 ($settings['direction_vertical'] ?? '') === 'yes' ? 'marqueex-marquee-vertical' : 'marqueex-marquee-horizontal',
1706 ])),
1707 'data-settings' => wp_json_encode(array_filter([
1708 'marquee_speed' => max(10, min(100, intval($settings['speed']['size'] ?? 30))),
1709 ])),
1710 'role' => 'region',
1711 'aria-label' => __('Text Marquee', 'marqueex'),
1712 ];
1713
1714 /**
1715 * Filter the render attributes for marqueex widgets so extensions
1716 * (e.g. marqueex-pro) can inject extra classes or data-* attributes.
1717 *
1718 * @param array $render_attrs Attribute map. `class` is an array of class names.
1719 * @param array $settings Widget settings from get_settings_for_display().
1720 * @param string $widget_name Elementor widget name (e.g. marqueex-text-marquee).
1721 */
1722 $render_attrs = apply_filters('marqueex_widget_render_attributes', $render_attrs, $settings, $this->get_name());
1723
1724 $this->add_render_attribute('marqueex-marquee-block', $render_attrs);
1725
1726 // Render the track content once and reuse it for the aria-hidden clone,
1727 // avoiding a second identical post query per widget render.
1728 ob_start();
1729 $this->render_ticker_content_by_type($settings, $widget_config);
1730 $track_html = ob_get_clean();
1731
1732 ?>
1733 <?php // get_render_attribute_string() returns markup already escaped by Elementor. ?>
1734 <div <?php echo $this->get_render_attribute_string('marqueex-marquee-block'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
1735 <div class="marqueex-marquee-track-wrapper">
1736 <div class="marqueex-marquee-track">
1737 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
1738 </div>
1739 <div aria-hidden="true" class="marqueex-marquee-track">
1740 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
1741 </div>
1742 </div>
1743 </div>
1744
1745 <?php
1746 }
1747 }
1748