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

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

838 lines 28.9 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 Wpxero\Marqueex\Core\Upsell;
8 use Elementor\Group_Control_Typography;
9 use Elementor\Group_Control_Border;
10 use Elementor\Group_Control_Box_Shadow;
11 use Wpxero\Marqueex\Traits\PostQuery\PostQueryControls;
12
13 if (!defined('ABSPATH')) {
14 exit;
15 }
16
17 class PostMarquee extends Widget_Base {
18
19 use PostQueryControls;
20
21 public function get_name() {
22 return 'marqueex-post-marquee';
23 }
24
25 public function get_title() {
26 return __('Post Marquee', 'marqueex');
27 }
28
29 public function get_icon() {
30 return 'eicon-posts-carousel eicon-marqueex';
31 }
32
33 public function get_categories() {
34 return ['marqueex'];
35 }
36
37 public function get_style_depends() {
38 return ['marqueex-post-marquee'];
39 }
40
41 public function get_script_depends() {
42 return ['marqueex-post-marquee'];
43 }
44
45 public function get_keywords() {
46 return ['post', 'grid', 'marquee', 'blog', 'posts', 'slider', 'scroll'];
47 }
48
49 protected function register_controls() {
50
51
52 $this->start_controls_section(
53 'post_content',
54 ['label' => __('Post Content', 'marqueex')]
55 );
56
57 $this->add_control(
58 'show_author',
59 [
60 'label' => __('Show Author', 'marqueex'),
61 'type' => Controls_Manager::SWITCHER,
62 'default' => 'yes',
63 ]
64 );
65
66 $this->add_control(
67 'show_date',
68 [
69 'label' => __('Show Date', 'marqueex'),
70 'type' => Controls_Manager::SWITCHER,
71 'default' => 'yes',
72 ]
73 );
74
75 $this->add_control(
76 'show_category',
77 [
78 'label' => __('Show Category', 'marqueex'),
79 'type' => Controls_Manager::SWITCHER,
80 'default' => 'yes',
81 ]
82 );
83
84 // $this->add_control(
85 // 'show_excerpt',
86 // [
87 // 'label' => __('Show Excerpt', 'marqueex'),
88 // 'type' => Controls_Manager::SWITCHER,
89 // 'default' => 'yes',
90 // ]
91 // );
92
93 $this->add_control(
94 'excerpt_length',
95 [
96 'label' => __('Excerpt Word Limit', 'marqueex'),
97 'type' => Controls_Manager::NUMBER,
98 'default' => 15,
99 'min' => 0,
100 'max' => 100,
101 'condition' => ['show_excerpt' => 'yes'],
102 ]
103 );
104
105 $this->add_control(
106 'meta_separator',
107 [
108 'label' => __('Meta Separator', 'marqueex'),
109 'type' => Controls_Manager::TEXT,
110 'default' => '-',
111 'label_block' => false,
112 ]
113 );
114 // ── Content layout (Pro) ──
115 // Shown to everyone, labelled "(Pro)" without a license, so free users can
116 // see the placements by name. render() clamps to the free default.
117 $free_positions = ['bottom-left' => __('Bottom Left', 'marqueex')];
118 $pro_positions = [
119 'top-left' => __('Top Left', 'marqueex'),
120 'top-right' => __('Top Right', 'marqueex'),
121 'center-center' => __('Center Center', 'marqueex'),
122 'bottom-center' => __('Bottom Center', 'marqueex'),
123 'bottom-right' => __('Bottom Right', 'marqueex'),
124 ];
125
126 $free_styles = ['overlay' => __('Overlay', 'marqueex')];
127 $pro_styles = [
128 'blury' => __('Blury', 'marqueex'),
129 'lighten' => __('Lighten', 'marqueex'),
130 ];
131
132 $this->add_control(
133 'content_position',
134 [
135 'label' => __('Content Position', 'marqueex'),
136 'type' => Controls_Manager::SELECT,
137 'default' => 'bottom-left',
138 'options' => Upsell::merge_select_options($free_positions, $pro_positions),
139 ]
140 );
141
142 $this->add_control(
143 'content_style',
144 [
145 'label' => __('Content Style', 'marqueex'),
146 'type' => Controls_Manager::SELECT,
147 'default' => 'overlay',
148 'options' => Upsell::merge_select_options($free_styles, $pro_styles),
149 ]
150 );
151
152 Upsell::add_elementor_notice($this, 'marqueex_post_layout_pro_notice', [
153 'title' => __('Card layout controls', 'marqueex'),
154 'description' => __('Place the title, date and category anywhere on the card, and pick how the text sits over the image.', 'marqueex'),
155 'items' => [
156 __('Six content placements', 'marqueex'),
157 __('Overlay, Blury and Lighten text treatments', 'marqueex'),
158 ],
159 'cta' => __('Unlock card layouts', 'marqueex'),
160 'source' => 'elementor-post-marquee-layout',
161 ]);
162
163 $this->add_control(
164 'global_link',
165 [
166 'label' => __('Item Wrapper Link', 'marqueex'),
167 'type' => Controls_Manager::SWITCHER,
168 'prefix_class' => 'marqueex-global-link-',
169 'description' => __('Be aware! When Item Wrapper Link activated then title link will not work', 'marqueex'),
170 ]
171 );
172
173
174 $this->end_controls_section();
175
176 // Query controls from PostQueryControls trait
177 $this->register_query_controls();
178
179 $this->start_controls_section(
180 'marquee_settings',
181 ['label' => __('Marquee Settings', 'marqueex')]
182 );
183
184 $this->add_control(
185 'orientation',
186 [
187 'label' => __('Orientation', 'marqueex'),
188 'type' => Controls_Manager::SELECT,
189 'default' => 'horizontal',
190 'options' => [
191 'horizontal' => __('Horizontal', 'marqueex'),
192 'vertical' => __('Vertical', 'marqueex'),
193 ],
194 ]
195 );
196
197 $this->add_control(
198 'vertical_height',
199 [
200 'label' => __('Height', 'marqueex'),
201 'type' => Controls_Manager::SLIDER,
202 'size_units' => ['vh', 'px'],
203 'range' => [
204 'vh' => ['min' => 20, 'max' => 100],
205 'px' => ['min' => 200, 'max' => 1000],
206 ],
207 'default' => ['size' => 60, 'unit' => 'vh'],
208 'condition' => ['orientation' => 'vertical'],
209 'selectors' => [
210 '{{WRAPPER}} .marqueex-pgm-wrap' => 'height: {{SIZE}}{{UNIT}};',
211 ],
212 ]
213 );
214
215 $this->add_control(
216 'reverse',
217 [
218 'label' => __('Reverse Direction', 'marqueex'),
219 'type' => Controls_Manager::SWITCHER,
220 'return_value' => 'yes',
221 ]
222 );
223
224 $this->add_control(
225 'pause',
226 [
227 'label' => __('Pause on Hover', 'marqueex'),
228 'type' => Controls_Manager::SWITCHER,
229 'return_value' => 'yes',
230 'default' => 'yes',
231 ]
232 );
233
234 $this->add_control(
235 'speed',
236 [
237 'label' => __('Speed (px/s)', 'marqueex'),
238 'type' => Controls_Manager::SLIDER,
239 'range' => ['px' => ['min' => 1, 'max' => 200]],
240 'default' => ['size' => 20, 'unit' => 'px'],
241 ]
242 );
243
244 $this->add_responsive_control(
245 'gap',
246 [
247 'label' => __('Gap', 'marqueex'),
248 'type' => Controls_Manager::SLIDER,
249 'size_units' => ['px', 'em', 'rem'],
250 'range' => [
251 'px' => ['min' => 0, 'max' => 100, 'step' => 1],
252 'em' => ['min' => 0, 'max' => 10, 'step' => 0.1],
253 'rem' => ['min' => 0, 'max' => 10, 'step' => 0.1],
254 ],
255 'default' => ['size' => 24, 'unit' => 'px'],
256 'selectors' => [
257 '{{WRAPPER}} .marqueex-marquee-track-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
258 '{{WRAPPER}} .marqueex-marquee-track' => 'gap: {{SIZE}}{{UNIT}};',
259 '{{WRAPPER}} .marqueex-marquee-wrapper' => 'gap: {{SIZE}}{{UNIT}};',
260 ],
261 ]
262 );
263
264 $this->add_control(
265 'mask_edges',
266 [
267 'label' => __('Soft Edge Mask', 'marqueex'),
268 'type' => Controls_Manager::SWITCHER,
269 'return_value' => 'yes',
270 ]
271 );
272
273 $this->end_controls_section();
274
275 $this->start_controls_section(
276 'style_overlay',
277 [
278 'label' => __('Overlay & Effects', 'marqueex'),
279 'tab' => Controls_Manager::TAB_STYLE,
280 ]
281 );
282
283 $this->add_control(
284 'overlay_background',
285 [
286 'label' => __('Overlay Color', 'marqueex'),
287 'type' => Controls_Manager::COLOR,
288 'selectors' => [
289 '{{WRAPPER}} .marqueex-post-marquee .marqueex-item-box:before' => 'background-color: {{VALUE}};',
290 ],
291 ]
292 );
293
294 $this->add_control(
295 'overlay_blur_effect',
296 [
297 'label' => __('Glassmorphism', 'marqueex'),
298 'type' => Controls_Manager::SWITCHER,
299 'description' => __('This feature will not work in the Firefox browser until you enable browser compatibility', 'marqueex'),
300 'default' => 'yes',
301 'condition' => [
302 'content_style' => 'blury',
303 ]
304 ]
305 );
306
307 $this->add_control(
308 'overlay_blur_level',
309 [
310 'label' => __('Blur Level', 'marqueex'),
311 'type' => Controls_Manager::SLIDER,
312 'range' => [
313 'px' => [
314 'min' => 0,
315 'step' => 1,
316 'max' => 50,
317 ]
318 ],
319 'default' => [
320 'size' => 10
321 ],
322 'selectors' => [
323 '{{WRAPPER}} .marqueex-post-marquee .marqueex-content' => 'backdrop-filter: blur({{SIZE}}px); -webkit-backdrop-filter: blur({{SIZE}}px);'
324 ],
325 'condition' => [
326 'overlay_blur_effect' => 'yes',
327 'content_style' => 'blury',
328 ]
329 ]
330 );
331
332 $this->end_controls_section();
333
334 $this->start_controls_section(
335 'style_category',
336 [
337 'label' => __('Category', 'marqueex'),
338 'tab' => Controls_Manager::TAB_STYLE,
339 ]
340 );
341
342 $this->add_control(
343 'category_color',
344 [
345 'label' => __('Text Color', 'marqueex'),
346 'type' => Controls_Manager::COLOR,
347 'default' => '#ffffff',
348 'selectors' => [
349 '{{WRAPPER}} .marqueex-category a' => 'color: {{VALUE}};',
350 ],
351 ]
352 );
353
354 $this->add_control(
355 'category_background',
356 [
357 'label' => __('Background Color', 'marqueex'),
358 'type' => Controls_Manager::COLOR,
359 'default' => '#ef233c',
360 'selectors' => [
361 '{{WRAPPER}} .marqueex-category a' => 'background-color: {{VALUE}};',
362 ],
363 ]
364 );
365
366 $this->add_group_control(
367 Group_Control_Typography::get_type(),
368 [
369 'name' => 'category_typography',
370 'selector' => '{{WRAPPER}} .marqueex-category a',
371 ]
372 );
373
374 $this->end_controls_section();
375
376 $this->start_controls_section(
377 'style_thumb',
378 [
379 'label' => __('Featured Image', 'marqueex'),
380 'tab' => Controls_Manager::TAB_STYLE,
381 ]
382 );
383
384 $this->add_responsive_control(
385 'thumb_width',
386 [
387 'label' => __('Width', 'marqueex'),
388 'type' => Controls_Manager::SLIDER,
389 'size_units' => ['px', '%', 'em'],
390 'range' => [
391 'px' => ['min' => 50, 'max' => 2000],
392 '%' => ['min' => 10, 'max' => 100],
393 'em' => ['min' => 1, 'max' => 50],
394 ],
395 'selectors' => [
396 '{{WRAPPER}} .marqueex-img' => 'width: {{SIZE}}{{UNIT}};',
397 ],
398 ]
399 );
400
401 $this->add_responsive_control(
402 'thumb_height',
403 [
404 'label' => __('Height', 'marqueex'),
405 'type' => Controls_Manager::SLIDER,
406 'size_units' => ['px', '%', 'em'],
407 'range' => [
408 'px' => ['min' => 50, 'max' => 2000],
409 '%' => ['min' => 10, 'max' => 100],
410 'em' => ['min' => 1, 'max' => 50],
411 ],
412 'selectors' => [
413 '{{WRAPPER}} .marqueex-img' => 'height: {{SIZE}}{{UNIT}};',
414 ],
415 ]
416 );
417
418 $this->add_responsive_control(
419 'thumb_border_radius',
420 [
421 'label' => __('Border Radius', 'marqueex'),
422 'type' => Controls_Manager::DIMENSIONS,
423 'selectors' => [
424 '{{WRAPPER}} .marqueex-img' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
425 ],
426 ]
427 );
428
429 $this->add_group_control(
430 Group_Control_Box_Shadow::get_type(),
431 [
432 'name' => 'thumb_shadow',
433 'selector' => '{{WRAPPER}} .marqueex-img',
434 ]
435 );
436
437 $this->end_controls_section();
438
439 $this->start_controls_section(
440 'style_title',
441 [
442 'label' => __('Post Title', 'marqueex'),
443 'tab' => Controls_Manager::TAB_STYLE,
444 ]
445 );
446
447 $this->add_control(
448 'title_color',
449 [
450 'label' => __('Text Color', 'marqueex'),
451 'type' => Controls_Manager::COLOR,
452 'selectors' => [
453 '{{WRAPPER}} .marqueex-title a' => 'color: {{VALUE}};',
454 ],
455 ]
456 );
457
458 $this->add_control(
459 'title_hover_color',
460 [
461 'label' => __('Hover Color', 'marqueex'),
462 'type' => Controls_Manager::COLOR,
463 'selectors' => [
464 '{{WRAPPER}} .marqueex-title a:hover' => 'color: {{VALUE}};',
465 ],
466 ]
467 );
468
469 $this->add_group_control(
470 Group_Control_Typography::get_type(),
471 [
472 'name' => 'title_typo',
473 'selector' => '{{WRAPPER}} .marqueex-title a',
474 ]
475 );
476
477 $this->end_controls_section();
478
479 $this->start_controls_section(
480 'style_meta',
481 [
482 'label' => __('Meta (Author & Date)', 'marqueex'),
483 'tab' => Controls_Manager::TAB_STYLE,
484 ]
485 );
486
487 $this->add_control(
488 'meta_color',
489 [
490 'label' => __('Text Color', 'marqueex'),
491 'type' => Controls_Manager::COLOR,
492 'selectors' => [
493 '{{WRAPPER}} .marqueex-meta, {{WRAPPER}} .marqueex-meta i' => 'color: {{VALUE}};',
494 ],
495 ]
496 );
497
498 $this->add_group_control(
499 Group_Control_Typography::get_type(),
500 [
501 'name' => 'meta_typo',
502 'selector' => '{{WRAPPER}} .marqueex-meta',
503 ]
504 );
505
506 $this->end_controls_section();
507
508 $this->start_controls_section(
509 'style_excerpt',
510 [
511 'label' => __('Excerpt', 'marqueex'),
512 'tab' => Controls_Manager::TAB_STYLE,
513 ]
514 );
515
516 $this->add_control(
517 'excerpt_color',
518 [
519 'label' => __('Text Color', 'marqueex'),
520 'type' => Controls_Manager::COLOR,
521 'selectors' => [
522 '{{WRAPPER}} .marqueex-excerpt' => 'color: {{VALUE}};',
523 ],
524 ]
525 );
526
527 $this->add_group_control(
528 Group_Control_Typography::get_type(),
529 [
530 'name' => 'excerpt_typo',
531 'selector' => '{{WRAPPER}} .marqueex-excerpt',
532 ]
533 );
534
535 $this->end_controls_section();
536
537 $this->start_controls_section(
538 'style_button',
539 [
540 'label' => __('Read More Button', 'marqueex'),
541 'tab' => Controls_Manager::TAB_STYLE,
542 ]
543 );
544
545 $this->add_control(
546 'btn_color',
547 [
548 'label' => __('Text Color', 'marqueex'),
549 'type' => Controls_Manager::COLOR,
550 'selectors' => [
551 '{{WRAPPER}} .marqueex-readmore' => 'color: {{VALUE}};',
552 ],
553 ]
554 );
555
556 $this->add_control(
557 'btn_bg',
558 [
559 'label' => __('Background Color', 'marqueex'),
560 'type' => Controls_Manager::COLOR,
561 'selectors' => [
562 '{{WRAPPER}} .marqueex-readmore' => 'background-color: {{VALUE}};',
563 ],
564 ]
565 );
566
567 $this->add_control(
568 'btn_hover_color',
569 [
570 'label' => __('Hover Text Color', 'marqueex'),
571 'type' => Controls_Manager::COLOR,
572 'selectors' => [
573 '{{WRAPPER}} .marqueex-readmore:hover' => 'color: {{VALUE}};',
574 ],
575 ]
576 );
577
578 $this->add_control(
579 'btn_hover_bg',
580 [
581 'label' => __('Hover Background', 'marqueex'),
582 'type' => Controls_Manager::COLOR,
583 'selectors' => [
584 '{{WRAPPER}} .marqueex-readmore:hover' => 'background-color: {{VALUE}};',
585 ],
586 ]
587 );
588
589 $this->add_group_control(
590 Group_Control_Border::get_type(),
591 [
592 'name' => 'btn_border',
593 'selector' => '{{WRAPPER}} .marqueex-readmore',
594 ]
595 );
596
597 $this->add_responsive_control(
598 'btn_border_radius',
599 [
600 'label' => __('Border Radius', 'marqueex'),
601 'type' => Controls_Manager::DIMENSIONS,
602 'selectors' => [
603 '{{WRAPPER}} .marqueex-readmore' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
604 ],
605 ]
606 );
607
608 $this->add_group_control(
609 Group_Control_Typography::get_type(),
610 [
611 'name' => 'btn_typo',
612 'selector' => '{{WRAPPER}} .marqueex-readmore',
613 ]
614 );
615
616 $this->end_controls_section();
617 }
618
619 /**
620 * Ensure minimum items for smooth marquee animation
621 */
622 private function ensure_minimum_posts($posts) {
623 if (empty($posts)) {
624 return [];
625 }
626
627 // Duplicate posts if we have less than 10 for smooth marquee
628 while (count($posts) < 10) {
629 $posts = array_merge($posts, $posts);
630 }
631
632 return $posts;
633 }
634
635 /**
636 * Prepare widget configuration from settings
637 *
638 * @param array $settings Widget settings from Elementor
639 * @return array Processed configuration array
640 */
641 private function prepare_widget_config($settings) {
642 return [
643 'unique_id' => 'marqueex-post-grid-' . $this->get_id(),
644 'speed' => max(10, min(400, intval($settings['speed']['size'] ?? 60))),
645 ];
646 }
647
648 /**
649 * Render post grid content
650 *
651 * @param array $settings Widget settings
652 */
653 private function render_post_grid_content($settings) {
654 $query_args = $this->get_query_args($settings);
655 $posts = get_posts($query_args);
656
657 if (empty($posts)) {
658 return;
659 }
660
661 $posts = $this->ensure_minimum_posts($posts);
662
663 echo '<div class="marqueex-marquee-wrapper">';
664 foreach ($posts as $post) {
665 $this->render_single_post_card($post, $settings);
666 }
667 echo '</div>';
668 }
669
670 /**
671 * Render single post card
672 *
673 * @param object $post Post object
674 * @param array $settings Widget settings
675 */
676 private function render_single_post_card($post, $settings) {
677 if (!$post instanceof \WP_Post) {
678 return;
679 }
680
681 setup_postdata($post);
682
683 $use_global_link = !empty($settings['global_link']) && $settings['global_link'] === 'yes';
684 $permalink = get_permalink($post->ID);
685
686 $image_src = wp_get_attachment_image_src(get_post_thumbnail_id($post->ID), 'large');
687 $placeholder = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgZmlsbD0iI2VlZSIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LXNpemU9IjE4IiBmaWxsPSIjOTk5IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBkeT0iLjNlbSI+Tm8gSW1hZ2U8L3RleHQ+PC9zdmc+';
688 $image_src = $image_src ? $image_src[0] : $placeholder;
689
690 $categories = get_the_category($post->ID);
691 $category_output = '';
692 if (!empty($categories) && ($settings['show_category'] ?? '') === 'yes') {
693 $category_links = [];
694 foreach ($categories as $category) {
695 $category_links[] = '<a href="' . esc_url(get_category_link($category->term_id)) . '">' . esc_html($category->name) . '</a>';
696 }
697 $category_output = implode('', $category_links);
698 }
699
700 $item_tag = $use_global_link ? 'a' : 'div';
701 ?>
702 <<?php echo tag_escape($item_tag); ?> class="marqueex-item"<?php echo $use_global_link ? ' href="' . esc_url($permalink) . '"' : ''; ?>>
703 <div class="marqueex-item-box">
704 <img class="marqueex-img" src="<?php echo esc_url($image_src); ?>" alt="<?php echo esc_attr(get_the_title($post->ID)); ?>">
705
706 <div class="marqueex-content">
707 <?php if ($settings['show_category'] === 'yes' && !empty($category_output)) : ?>
708 <div class="marqueex-category">
709 <?php echo wp_kses_post($category_output); ?>
710 </div>
711 <?php endif; ?>
712
713 <?php if (!isset($settings['show_title']) || $settings['show_title'] === 'yes') : ?>
714 <div class="marqueex-title-wrap">
715 <h3 class="marqueex-title">
716 <?php if ($use_global_link) : ?>
717 <span><?php echo esc_html(get_the_title($post->ID)); ?></span>
718 <?php else : ?>
719 <a href="<?php echo esc_url($permalink); ?>" title="<?php echo esc_attr(get_the_title($post->ID)); ?>">
720 <?php echo esc_html(get_the_title($post->ID)); ?>
721 </a>
722 <?php endif; ?>
723 </h3>
724 </div>
725 <?php endif; ?>
726
727 <?php if (
728 $settings['show_author'] === 'yes' ||
729 $settings['show_date'] === 'yes'
730 ) : ?>
731 <div class="marqueex-meta">
732 <?php if ($settings['show_author'] === 'yes') : ?>
733 <div class="marqueex-blog-author">
734 <span class="by">
735 <?php echo esc_html_x('by', 'Frontend', 'marqueex'); ?>
736 </span>
737 <span class="marqueex-post-grid-author">
738 <a href="<?php echo esc_url(get_author_posts_url($post->post_author)); ?>">
739 <?php echo esc_html(get_the_author_meta('display_name', $post->post_author)); ?>
740 </a>
741 </span>
742 </div>
743 <?php endif; ?>
744
745 <?php if ($settings['show_date'] === 'yes') : ?>
746 <div data-separator="<?php echo esc_html($settings['meta_separator']); ?>">
747 <div class="marqueex-date">
748 <i class="far fa-calendar" aria-hidden="true"></i>
749 <?php echo esc_html(get_the_date('', $post->ID)); ?>
750 </div>
751 </div>
752 <?php endif; ?>
753 </div>
754 <?php endif; ?>
755
756 <?php if (($settings['show_excerpt'] ?? '') === 'yes') :
757 $excerpt_length = intval($settings['excerpt_length'] ?? 15);
758 $excerpt = wp_trim_words(get_the_excerpt($post->ID), $excerpt_length, '...');
759 if (!empty($excerpt)) : ?>
760 <div class="marqueex-excerpt">
761 <?php echo esc_html($excerpt); ?>
762 </div>
763 <?php endif; ?>
764 <?php endif; ?>
765 </div>
766 </div>
767 </<?php echo tag_escape($item_tag); ?>>
768 <?php
769 wp_reset_postdata();
770 }
771
772 /**
773 * Render widget output on the frontend
774 */
775 protected function render() {
776 $settings = $this->get_settings_for_display();
777 $is_pro_active = class_exists('\\Wpxero\\Marqueex\\MarqueeX')
778 && \Wpxero\Marqueex\MarqueeX::is_premium_active();
779 $widget_config = $this->prepare_widget_config($settings);
780
781 // Pro placements/styles are selectable without a license so free users can
782 // see they exist; the front end falls back to the free value.
783 $free_positions = ['bottom-left'];
784 $free_styles = ['overlay'];
785
786 $content_position = $settings['content_position'] ?? 'bottom-left';
787 if (!$is_pro_active && !in_array($content_position, $free_positions, true)) {
788 $content_position = 'bottom-left';
789 }
790
791 $content_style = $settings['content_style'] ?? 'overlay';
792 if (!$is_pro_active && !in_array($content_style, $free_styles, true)) {
793 $content_style = 'overlay';
794 }
795
796 $this->add_render_attribute(
797 [
798 'marqueex-post-marquee' => [
799 'class' => array_values(array_filter([
800 'marqueex-marquee-block',
801 'marqueex-post-marquee',
802 'marqueex-text-position-' . sanitize_html_class($content_position),
803 'marqueex-content-style-' . sanitize_html_class($content_style),
804 ($settings['pause'] ?? '') === 'yes' ? 'is-paused' : '',
805 ($settings['reverse'] ?? '') === 'yes' ? 'is-reversed' : '',
806 ($settings['orientation'] ?? '') === 'vertical' ? 'marqueex-marquee-vertical' : 'marqueex-marquee-horizontal',
807 ($settings['mask_edges'] ?? '') === 'yes' ? 'marqueex-marquee-edge-shadow' : '',
808 ])),
809 'data-speed' => $widget_config['speed'],
810 'role' => 'region',
811 'aria-label' => __('Post Grid Marquee', 'marqueex'),
812 ]
813 ]
814 );
815
816 // Render the post grid once and reuse it for the aria-hidden clone,
817 // avoiding a second identical post query per widget render.
818 ob_start();
819 $this->render_post_grid_content($settings);
820 $track_html = ob_get_clean();
821
822 ?>
823 <?php // get_render_attribute_string() returns markup already escaped by Elementor. ?>
824 <div <?php echo $this->get_render_attribute_string('marqueex-post-marquee'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
825 <div class="marqueex-marquee-track-wrapper">
826 <div class="marqueex-marquee-track">
827 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
828 </div>
829 <div aria-hidden="true" class="marqueex-marquee-track">
830 <?php echo $track_html; // phpcs:ignore WordPress.Security.EscapeOutput ?>
831 </div>
832 </div>
833 </div>
834
835 <?php
836 }
837 }
838