register()` — the APIs * used below — arrived in 3.5.0. On anything older the widgets silently fail * to register, which previously looked like a broken plugin. */ const MIN_ELEMENTOR_VERSION = '3.5.0'; /** * Constructor */ private function __construct() { add_action('init', [$this, 'init']); } /** * Initialize Elementor integration */ public function init() { // Check if Elementor is active if (!$this->is_elementor_active()) { return; } if (!$this->is_elementor_supported()) { add_action('admin_notices', [$this, 'outdated_elementor_notice']); return; } // Check if integration is enabled $elementor_enabled = Settings::get('builder_support.elementor.enabled', true); if (!$elementor_enabled) { return; } // Hook into Elementor add_action('elementor/widgets/register', [$this, 'register_widgets']); add_action('elementor/elements/categories_registered', [$this, 'add_widget_categories'], 1); add_action('elementor/editor/before_enqueue_scripts', [$this, 'enqueue_editor_scripts']); // add_action('elementor/frontend/after_enqueue_scripts', [$this, 'enqueue_elementor_assets']); add_action('wp_enqueue_scripts', [$this, 'enqueue_elementor_assets']); // Marketing: advertise the Pro scroll-interaction features (mouse wheel + drag) // as a locked section inside the free marquee widgets. Hidden when premium is // active — the pro add-on then injects the real working controls instead. // Note: post-marquee's section id is `marquee_settings`; the others use // `marquee_settings_section`. add_action('elementor/element/marqueex-text-marquee/marquee_settings_section/after_section_end', [$this, 'add_interaction_upsell_section'], 15, 2); add_action('elementor/element/marqueex-image-marquee/marquee_settings_section/after_section_end', [$this, 'add_interaction_upsell_section'], 15, 2); add_action('elementor/element/marqueex-news-ticker/marquee_settings_section/after_section_end', [$this, 'add_interaction_upsell_section'], 15, 2); add_action('elementor/element/marqueex-post-marquee/marquee_settings/after_section_end', [$this, 'add_interaction_upsell_section'], 15, 2); } /** * Add a locked "Scroll Interactions" section (Mouse Wheel + Drag) to free * marquee widgets. Shown only to non-premium users — marqueex-pro registers * the real working section at priority 20 when licensed. */ public function add_interaction_upsell_section($element, $args) { if (!Upsell::should_show()) { return; } $element->start_controls_section( 'marqueex_interactions_upsell', [ 'label' => __('Scroll Interactions', 'marqueex'), 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, ] ); Upsell::add_elementor_notice($element, 'pro_interactions_notice', [ 'title' => __('Let visitors drive the marquee', 'marqueex'), 'description' => __('Turn a strip that scrolls past people into one they can grab. Visitors spin the wheel to speed it up or reverse it, or drag it to scrub through — and it eases back to your set speed on its own.', 'marqueex'), 'items' => [ __('Mouse wheel control with adjustable sensitivity', 'marqueex'), __('Drag to scroll, with flick momentum on release', 'marqueex'), __('Optional "scroll sets direction" mode', 'marqueex'), ], 'cta' => __('Unlock scroll interactions', 'marqueex'), 'source' => 'elementor-scroll-interactions', ]); $element->end_controls_section(); } /** * Check if Elementor is active */ private function is_elementor_active() { return class_exists('\Elementor\Plugin'); } /** * Whether the installed Elementor is new enough to register our widgets. * * @return bool */ private function is_elementor_supported() { if (!defined('ELEMENTOR_VERSION')) { // Version constant missing — assume supported rather than disabling the // integration on an unexpected build. return true; } return version_compare(ELEMENTOR_VERSION, self::MIN_ELEMENTOR_VERSION, '>='); } /** * Explain why the MarqueeX widgets are missing from the Elementor panel. */ public function outdated_elementor_notice() { if (!current_user_can('update_plugins')) { return; } ?>

*/ private function get_widget_definitions() { $frontend_deps = ['jquery', 'marqueex-elementor-frontend']; return [ 'image_marquee' => [ 'class' => 'ImageMarquee', 'style_handle' => 'marqueex-image-marquee', 'style_css' => 'image-marquee.css', 'script_handle' => 'marqueex-image-marquee', 'script_js' => 'image-marquee.js', 'script_deps' => $frontend_deps, ], 'text_marquee' => [ 'class' => 'TextMarquee', 'style_handle' => 'marqueex-text-marquee', 'style_css' => 'text-marquee.css', 'script_handle' => 'marqueex-text-marquee', 'script_js' => 'text-marquee.js', 'script_deps' => $frontend_deps, ], 'animated_heading' => [ 'class' => 'AnimatedHeading', 'style_handle' => 'marqueex-animated-heading-style', 'style_css' => 'animated-heading.css', 'script_handle' => 'marqueex-animated-heading', 'script_js' => 'animated-heading.js', // Animated Heading bundles its own animation logic; no frontend dep. 'script_deps' => ['jquery'], ], 'animated_word_roller' => [ 'class' => 'AnimatedWordRoller', 'style_handle' => 'marqueex-animated-word-roller-style', 'style_css' => 'animated-word-roller.css', 'script_handle' => 'marqueex-animated-word-roller', 'script_js' => 'animated-word-roller.js', 'script_deps' => $frontend_deps, ], 'news_ticker' => [ 'class' => 'NewsTicker', 'style_handle' => 'marqueex-news-ticker', 'style_css' => 'news-ticker.css', 'script_handle' => 'marqueex-news-ticker', 'script_js' => 'news-ticker.js', 'script_deps' => $frontend_deps, ], 'post_marquee' => [ 'class' => 'PostMarquee', 'style_handle' => 'marqueex-post-marquee', 'style_css' => 'post-marquee.css', 'script_handle' => 'marqueex-post-marquee', 'script_js' => 'post-marquee.js', 'script_deps' => $frontend_deps, ], 'team_members_marquee' => [ 'class' => 'TeamMembersMarquee', 'style_handle' => 'marqueex-team-members-marquee', 'style_css' => 'team-members-marquee.css', 'script_handle' => 'marqueex-team-members-marquee', 'script_js' => 'team-members-marquee.js', 'script_deps' => $frontend_deps, ], 'testimonial_marquee' => [ 'class' => 'TestimonialMarquee', 'style_handle' => 'marqueex-testimonial-marquee', 'style_css' => 'testimonial-marquee.css', 'script_handle' => 'marqueex-testimonial-marquee', 'script_js' => 'testimonial-marquee.js', 'script_deps' => $frontend_deps, ], ]; } /** * Whether a widget is enabled in settings (defaults to true). * * @param string $key Settings element key. * @return bool */ private function is_widget_enabled($key) { $elements = Settings::get('builder_support.elementor.elements', []); return (bool) ($elements[$key] ?? true); } /** * Register MarqueeX widgets */ public function register_widgets($widgets_manager) { foreach ($this->get_widget_definitions() as $key => $def) { if (!$this->is_widget_enabled($key)) { continue; } require_once __DIR__ . '/Widgets/' . $def['class'] . '.php'; $fqcn = __NAMESPACE__ . '\\Widgets\\' . $def['class']; $widgets_manager->register(new $fqcn()); } } /** * Add custom widget categories */ public function add_widget_categories($elements_manager) { $elements_manager->add_category( 'marqueex', [ 'title' => __('MarqueeX', 'marqueex'), 'icon' => 'fa fa-sliders', ] ); } /** * Enqueue editor scripts */ public function enqueue_editor_scripts() { wp_enqueue_style( 'marqueex-elementor-editor', plugins_url('/assets/css/elementor-editor.css', MARQUEEX_FILE), [], MARQUEEX_VERSION ); } /** * Enqueue frontend scripts */ public function enqueue_elementor_assets() { // Register (don't enqueue) the shared base styles. Each widget style // below depends on this handle, so common.css loads only on pages that // actually render a MarqueeX widget — not on every front-end page. wp_register_style( 'marqueex-common', plugins_url('/assets/css/common.css', MARQUEEX_FILE), [], MARQUEEX_VERSION ); wp_register_script( 'marqueex-elementor-frontend', plugins_url('/assets/js/elementor-frontend.js', MARQUEEX_FILE), ['jquery'], MARQUEEX_VERSION, true ); // Register each widget's style (chained to common.css) and script from // the canonical definition map. Elementor enqueues these on demand via // each widget's get_style_depends()/get_script_depends(). foreach ($this->get_widget_definitions() as $def) { wp_register_style( $def['style_handle'], plugins_url('/assets/css/' . $def['style_css'], MARQUEEX_FILE), ['marqueex-common'], MARQUEEX_VERSION ); wp_register_script( $def['script_handle'], plugins_url('/assets/js/' . $def['script_js'], MARQUEEX_FILE), $def['script_deps'], MARQUEEX_VERSION, true ); } } /** * Get integration status */ public function get_status() { $status = [ 'active' => false, 'version' => null, 'widgets_registered' => false, 'enabled_widgets' => [], ]; if ($this->is_elementor_active()) { $status['active'] = true; $status['version'] = \Elementor\Plugin::$instance->get_version(); // Derive enabled widgets from the same canonical definition map so // status can never drift from what register_widgets() actually does. $enabled_widgets = []; foreach (array_keys($this->get_widget_definitions()) as $key) { if ($this->is_widget_enabled($key)) { $enabled_widgets[] = $key; } } $status['enabled_widgets'] = $enabled_widgets; $status['widgets_registered'] = !empty($enabled_widgets); } return $status; } }