get_type(), array( 'container', 'e-flexbox', 'e-div-block' ), true ); } public static function register_controls( $element ): void { $controls = '\Elementor\Controls_Manager'; $element->start_controls_section( 'advanced_marquee_section', array( 'label' => __( 'Marquee Ticker', 'advanced-animation' ), 'tab' => $controls::TAB_LAYOUT ) ); $element->add_control( 'advanced_marquee_enable', array( 'label' => __( 'Enable Marquee', 'advanced-animation' ), 'type' => $controls::SWITCHER, 'default' => '', 'frontend_available' => true ) ); $condition = array( 'advanced_marquee_enable' => 'yes' ); $element->add_control( 'advanced_marquee_help', array( 'type' => $controls::RAW_HTML, 'raw' => esc_html__( 'Each direct child is one ticker item. Use native container spacing and child widths. The editing canvas stays stationary; open page Preview to see motion. Visual content and links are supported.', 'advanced-animation' ), 'condition' => $condition ) ); $element->add_control( 'advanced_marquee_direction', array( 'label' => __( 'Direction', 'advanced-animation' ), 'type' => $controls::SELECT, 'default' => 'left', 'condition' => $condition, 'frontend_available' => true, 'options' => array( 'left' => __( 'Left', 'advanced-animation' ), 'right' => __( 'Right', 'advanced-animation' ), 'up' => __( 'Up', 'advanced-animation' ), 'down' => __( 'Down', 'advanced-animation' ) ), ) ); $element->add_responsive_control( 'advanced_marquee_speed', array( 'label' => __( 'Speed (px/second)', 'advanced-animation' ), 'type' => $controls::NUMBER, 'default' => 60, 'min' => 0, 'max' => 1000, 'condition' => $condition, 'frontend_available' => true, 'description' => __( 'Use 0 for dragging without autoplay.', 'advanced-animation' ) ) ); $element->add_control( 'advanced_marquee_hover_speed', array( 'label' => __( 'Speed on hover', 'advanced-animation' ), 'type' => $controls::NUMBER, 'default' => 0, 'min' => 0, 'max' => 3, 'step' => 0.1, 'condition' => $condition, 'frontend_available' => true, 'description' => __( 'Multiplier: 0 pauses, 0.5 slows down, 1 keeps the same speed.', 'advanced-animation' ) ) ); foreach ( array( 'draggable' => __( 'Drag to scroll', 'advanced-animation' ), 'momentum' => __( 'Drag momentum', 'advanced-animation' ), 'fade' => __( 'Fade edges', 'advanced-animation' ), 'show_pause' => __( 'Show pause / play button', 'advanced-animation' ) ) as $key => $label ) { $show = $condition; if ( 'momentum' === $key ) { $show['advanced_marquee_draggable'] = 'yes'; } $element->add_control( 'advanced_marquee_' . $key, array( 'label' => $label, 'type' => $controls::SWITCHER, 'default' => 'yes', 'condition' => $show, 'frontend_available' => true ) ); } $element->add_responsive_control( 'advanced_marquee_fade_width', array( 'label' => __( 'Fade width', 'advanced-animation' ), 'type' => $controls::SLIDER, 'size_units' => array( 'px', '%' ), 'default' => array( 'size' => 60, 'unit' => 'px' ), 'range' => array( 'px' => array( 'min' => 0, 'max' => 300 ), '%' => array( 'min' => 0, 'max' => 50 ) ), 'condition' => $condition + array( 'advanced_marquee_fade' => 'yes' ), 'frontend_available' => true, ) ); $element->add_control( 'advanced_marquee_accessible_label', array( 'label' => __( 'Accessible label', 'advanced-animation' ), 'type' => $controls::TEXT, 'default' => __( 'Scrolling content', 'advanced-animation' ), 'condition' => $condition, 'frontend_available' => true ) ); $element->end_controls_section(); } public static function init_atomic(): void { if ( ! class_exists( '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type' ) || ! class_exists( '\Elementor\Modules\AtomicWidgets\Controls\Base\Atomic_Control_Base' ) ) { return; } require_once __DIR__ . '/class-elementor-marquee-control.php'; add_filter( 'elementor/atomic-widgets/props-schema', array( __CLASS__, 'schema' ), 5 ); add_filter( 'elementor/atomic-widgets/controls', array( __CLASS__, 'atomic_controls' ), 10, 2 ); } public static function schema( array $schema ): array { // The schema filter has no element argument. Expose controls/rendering only on layouts. $schema[ self::PROP ] = \Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type::make()->default( '' ); return $schema; } public static function atomic_controls( array $controls, $element ): array { if ( self::supported( $element ) ) { $controls[] = \Elementor\Modules\AtomicWidgets\Controls\Section::make()->set_id( 'advanced-marquee' )->set_label( __( 'Marquee Ticker', 'advanced-animation' ) )->set_items( array( WPAddons_Elementor_Marquee_Control::bind_to( self::PROP )->set_label( __( 'Marquee Ticker', 'advanced-animation' ) ) ) ); } return $controls; } public static function breakpoints(): array { $plugin = \Elementor\Plugin::$instance; if ( isset( $plugin->breakpoints ) ) { return $plugin->breakpoints->get_breakpoints_config(); } return array( 'tablet' => array( 'value' => 1024, 'direction' => 'max', 'is_enabled' => true ), 'mobile' => array( 'value' => 767, 'direction' => 'max', 'is_enabled' => true ) ); } private static function number( $value, float $default, float $max ): float { return is_numeric( $value ) && is_finite( (float) $value ) ? max( 0, min( $max, (float) $value ) ) : $default; } private static function flag( $value ): bool { return in_array( $value, array( 'yes', true, 1, '1' ), true ); } private static function fade_width( $value ): array { $value = is_array( $value ) ? $value : array(); $unit = '%' === ( $value['unit'] ?? '' ) ? '%' : 'px'; return array( 'size' => self::number( $value['size'] ?? null, 60, '%' === $unit ? 50 : 1000 ), 'unit' => $unit ); } public static function normalize( array $settings ): array { $get = static function( $key, $default ) use ( $settings ) { return $settings[ 'advanced_marquee_' . $key ] ?? $default; }; $direction = $get( 'direction', 'left' ); $config = array( 'enabled' => self::flag( $get( 'enable', '' ) ), 'direction' => in_array( $direction, array( 'left', 'right', 'up', 'down' ), true ) ? $direction : 'left', 'speed' => self::number( $get( 'speed', 60 ), 60, 1000 ), 'hoverSpeed' => self::number( $get( 'hover_speed', 0 ), 0, 3 ), 'draggable' => self::flag( $get( 'draggable', 'yes' ) ), 'momentum' => self::flag( $get( 'momentum', 'yes' ) ), 'fade' => self::flag( $get( 'fade', 'yes' ) ), 'fadeWidth' => self::fade_width( $get( 'fade_width', array( 'size' => 60, 'unit' => 'px' ) ) ), 'showPause' => self::flag( $get( 'show_pause', 'yes' ) ), 'label' => sanitize_text_field( $get( 'accessible_label', __( 'Scrolling content', 'advanced-animation' ) ) ), 'pauseLabel' => __( 'Pause scrolling', 'advanced-animation' ), 'playLabel' => __( 'Play scrolling', 'advanced-animation' ), 'previewMotion' => false, 'responsive' => array(), ); foreach ( self::breakpoints() as $name => $breakpoint ) { if ( empty( $breakpoint['is_enabled'] ) ) { continue; } $row = array( 'name' => $name, 'value' => (int) $breakpoint['value'], 'direction' => 'min' === $breakpoint['direction'] ? 'min' : 'max' ); if ( isset( $settings[ 'advanced_marquee_speed_' . $name ] ) && '' !== $settings[ 'advanced_marquee_speed_' . $name ] ) { $row['speed'] = self::number( $settings[ 'advanced_marquee_speed_' . $name ], 60, 1000 ); } $fade = $settings[ 'advanced_marquee_fade_width_' . $name ] ?? null; if ( is_array( $fade ) && isset( $fade['size'] ) && '' !== $fade['size'] ) { $row['fadeWidth'] = self::fade_width( $fade ); } $config['responsive'][] = $row; } return $config; } public static function config( $element ): ?array { if ( ! self::supported( $element ) ) { return null; } if ( method_exists( $element, 'get_atomic_setting' ) ) { $value = $element->get_atomic_setting( self::PROP ); $settings = is_string( $value ) ? json_decode( $value, true ) : null; } else { $settings = $element->get_settings_for_display(); } if ( ! is_array( $settings ) ) { return null; } $config = self::normalize( $settings ); return $config['enabled'] ? $config : null; } public static function before_render( $element ): void { $config = self::config( $element ); if ( ! $config ) { return; } WPAddons_Elementor_Marquee::register_assets(); $element->add_script_depends( 'advanced-addons-marquee' ); $element->add_style_depends( 'advanced-addons-marquee' ); wp_enqueue_script( 'advanced-addons-marquee' ); wp_enqueue_style( 'advanced-addons-marquee' ); if ( method_exists( $element, 'get_atomic_setting' ) ) { self::$buffers[ spl_object_id( $element ) ] = array( 'level' => ob_get_level() + 1, 'config' => $config ); ob_start(); } else { $element->add_render_attribute( '_wrapper', array( 'data-marquee-container' => 'yes', 'data-marquee-config' => wp_json_encode( $config ) ) ); } } public static function after_render( $element ): void { $id = spl_object_id( $element ); if ( ! isset( self::$buffers[ $id ] ) ) { return; } $buffer = self::$buffers[ $id ]; unset( self::$buffers[ $id ] ); if ( ob_get_level() !== $buffer['level'] ) { return; } $html = ob_get_clean(); $processor = new WP_HTML_Tag_Processor( $html ); while ( $processor->next_tag() ) { if ( (string) $processor->get_attribute( 'data-id' ) !== (string) $element->get_id() ) { continue; } $processor->set_attribute( 'data-marquee-container', 'yes' ); $processor->set_attribute( 'data-marquee-config', wp_json_encode( $buffer['config'] ) ); break; } echo $processor->get_updated_html(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Only escaped attributes are inserted. } } add_action( 'elementor/element/container/section_layout_container/after_section_end', array( 'WPAddons_Elementor_Marquee_Container', 'register_controls' ) ); add_action( 'elementor/init', array( 'WPAddons_Elementor_Marquee_Container', 'init_atomic' ) ); add_action( 'elementor/frontend/before_render', array( 'WPAddons_Elementor_Marquee_Container', 'before_render' ), 20 ); add_action( 'elementor/frontend/after_render', array( 'WPAddons_Elementor_Marquee_Container', 'after_render' ), 20 );