elementor_widgets_manager = $elementor_widgets_manager; $this->register_widget_assets(); $this->load_and_register_widgets(); } /** * Register CSS/JS assets for all widgets so Elementor can enqueue * them on-demand via get_style_depends() / get_script_depends(). */ private function register_widget_assets() { // stagger-testimonials wp_register_style( 'aafe-stagger-testimonials-style', AAFE_URL . 'widgets/stagger-testimonials/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/stagger-testimonials/assets/css/style.css' ) ); wp_register_script( 'aafe-stagger-testimonials-script', AAFE_URL . 'widgets/stagger-testimonials/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/stagger-testimonials/assets/js/script.js' ), true ); // bubble-button wp_register_style( 'aafe-bubble-button-style', AAFE_URL . 'widgets/bubble-button/assets/css/style.css', [], AAFE_VERSION ); // circle-button wp_register_style( 'aafe-circle-button-style', AAFE_URL . 'widgets/circle-button/assets/css/style.css', [], AAFE_VERSION ); // hover-reveal-card wp_register_style( 'aafe-hover-reveal-card-style', AAFE_URL . 'widgets/hover-reveal-card/assets/css/style.css', [], AAFE_VERSION ); // content-showcase wp_register_style( 'aafe-content-showcase-style', AAFE_URL . 'widgets/content-showcase/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-content-showcase-script', AAFE_URL . 'widgets/content-showcase/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // icon-cards wp_register_style( 'aafe-icon-cards-style', AAFE_URL . 'widgets/icon-cards/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/icon-cards/assets/css/style.css' ) ); // hover-image-list wp_register_style( 'aafe-hover-image-list-style', AAFE_URL . 'widgets/hover-image-list/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-hover-image-list-script', AAFE_URL . 'widgets/hover-image-list/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // animated-profile-card wp_register_style( 'aafe-animated-profile-card-style', AAFE_URL . 'widgets/animated-profile-card/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-animated-profile-card-script', AAFE_URL . 'widgets/animated-profile-card/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // testimonials-columns wp_register_style( 'aafe-testimonials-columns-style', AAFE_URL . 'widgets/testimonials-columns/assets/css/style.css', [], AAFE_VERSION ); // services-showcase wp_register_style( 'aafe-services-showcase-style', AAFE_URL . 'widgets/services-showcase/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-services-showcase-script', AAFE_URL . 'widgets/services-showcase/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // shuffle-hero wp_register_style( 'aafe-shuffle-hero-style', AAFE_URL . 'widgets/shuffle-hero/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/shuffle-hero/assets/css/style.css' ) ); wp_register_script( 'aafe-shuffle-hero-script', AAFE_URL . 'widgets/shuffle-hero/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/shuffle-hero/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // image-3d-carousel wp_register_style( 'aafe-image-3d-carousel-style', AAFE_URL . 'widgets/image-3d-carousel/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/image-3d-carousel/assets/css/style.css' ) ); wp_register_script( 'aafe-image-3d-carousel-script', AAFE_URL . 'widgets/image-3d-carousel/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/image-3d-carousel/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // marquee-gallery-cta wp_register_style( 'aafe-marquee-gallery-cta-style', AAFE_URL . 'widgets/marquee-gallery-cta/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/marquee-gallery-cta/assets/css/style.css' ) ); // product-features wp_register_style( 'aafe-product-features-style', AAFE_URL . 'widgets/product-features/assets/css/style.css', [], AAFE_VERSION ); // transaction-carousel wp_register_style( 'aafe-transaction-carousel-style', AAFE_URL . 'widgets/transaction-carousel/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-transaction-carousel-script', AAFE_URL . 'widgets/transaction-carousel/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // loop-grid wp_register_style( 'aafe-loop-grid-style', AAFE_URL . 'assets/css/loop-grid.css', [], AAFE_VERSION ); wp_register_script( 'aafe-loop-grid-script', AAFE_URL . 'widgets/loop-grid/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, [ 'in_footer' => true ] ); // bounce-image-content wp_register_style( 'aafe-bounce-image-content-style', AAFE_URL . 'widgets/bounce-image-content/assets/css/style.css', [], AAFE_VERSION ); // orbit-image-showcase wp_register_style( 'aafe-orbit-image-showcase-style', AAFE_URL . 'widgets/orbit-image-showcase/assets/css/style.css', [], AAFE_VERSION ); // AAFE FIX BUG#6: Register missing orbit-image-showcase JS (file exists but was never registered). wp_register_script( 'aafe-orbit-image-showcase-script', AAFE_URL . 'widgets/orbit-image-showcase/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // expanding-cards wp_register_style( 'aafe-expanding-cards-style', AAFE_URL . 'widgets/expanding-cards/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/expanding-cards/assets/css/style.css' ) ); wp_register_script( 'aafe-expanding-cards-script', AAFE_URL . 'widgets/expanding-cards/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/expanding-cards/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // image-sphere wp_register_style( 'aafe-image-sphere-style', AAFE_URL . 'widgets/image-sphere/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-image-sphere-script', AAFE_URL . 'widgets/image-sphere/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // marquee-radio-select wp_register_style( 'aafe-marquee-radio-select-style', AAFE_URL . 'widgets/marquee-radio-select/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/marquee-radio-select/assets/css/style.css' ) ); wp_register_script( 'aafe-marquee-radio-select-script', AAFE_URL . 'widgets/marquee-radio-select/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/marquee-radio-select/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // team-showcase wp_register_style( 'aafe-team-showcase-style', AAFE_URL . 'widgets/team-showcase/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-team-showcase-script', AAFE_URL . 'widgets/team-showcase/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // nav-menu wp_register_style( 'aafe-nav-menu-style', AAFE_URL . 'widgets/nav-menu/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-nav-menu-script', AAFE_URL . 'widgets/nav-menu/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // spaced-text-banner wp_register_style( 'aafe-spaced-text-banner-style', AAFE_URL . 'widgets/spaced-text-banner/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-spaced-text-banner-script', AAFE_URL . 'widgets/spaced-text-banner/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // animated-hike-card wp_register_style( 'aafe-animated-hike-card-style', AAFE_URL . 'widgets/animated-hike-card/assets/css/style.css', [], AAFE_VERSION ); // accordion-feature-section wp_register_style( 'aafe-accordion-feature-section-style', AAFE_URL . 'widgets/accordion-feature-section/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/accordion-feature-section/assets/css/style.css' ) ); wp_register_script( 'aafe-accordion-feature-section-script', AAFE_URL . 'widgets/accordion-feature-section/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/accordion-feature-section/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // linear-card wp_register_style( 'aafe-linear-card-style', AAFE_URL . 'widgets/linear-card/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-linear-card-script', AAFE_URL . 'widgets/linear-card/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // headline-gallery wp_register_style( 'aafe-headline-gallery-style', AAFE_URL . 'widgets/headline-gallery/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/headline-gallery/assets/css/style.css' ) ); wp_register_script( 'aafe-headline-gallery-script', AAFE_URL . 'widgets/headline-gallery/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/headline-gallery/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // marquee-button wp_register_style( 'aafe-marquee-button-style', AAFE_URL . 'widgets/marquee-button/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/marquee-button/assets/css/style.css' ) ); // slide-icon-button wp_register_style( 'aafe-slide-icon-button-style', AAFE_URL . 'widgets/slide-icon-button/assets/css/style.css', [], AAFE_VERSION ); // feature-tabs wp_register_style( 'aafe-feature-tabs-style', AAFE_URL . 'widgets/feature-tabs/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/feature-tabs/assets/css/style.css' ) ); wp_register_script( 'aafe-feature-tabs-script', AAFE_URL . 'widgets/feature-tabs/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // Register locally bundled Lottie Player wp_register_script( 'aafe-lottie-player', AAFE_URL . 'assets/js/lottie-player.js', [], '2.0.4', array( 'in_footer' => true, 'strategy' => 'defer' ) ); // dual-marquee-hero wp_register_style( 'aafe-dual-marquee-hero-style', AAFE_URL . 'widgets/dual-marquee-hero/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/dual-marquee-hero/assets/css/style.css' ) ); // kinetic-stagger-button wp_register_style( 'aafe-kinetic-stagger-button-style', AAFE_URL . 'widgets/kinetic-stagger-button/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-kinetic-stagger-button-script', AAFE_URL . 'widgets/kinetic-stagger-button/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // morphing-feature-pills wp_register_style( 'aafe-morphing-feature-pills-style', AAFE_URL . 'widgets/morphing-feature-pills/assets/css/style.css', [], file_exists( AAFE_PATH . 'widgets/morphing-feature-pills/assets/css/style.css' ) ? (string) filemtime( AAFE_PATH . 'widgets/morphing-feature-pills/assets/css/style.css' ) : AAFE_VERSION ); wp_register_script( 'aafe-morphing-feature-pills-script', AAFE_URL . 'widgets/morphing-feature-pills/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], file_exists( AAFE_PATH . 'widgets/morphing-feature-pills/assets/js/script.js' ) ? (string) filemtime( AAFE_PATH . 'widgets/morphing-feature-pills/assets/js/script.js' ) : AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // fx-slider wp_register_style( 'aafe-fx-slider-style', AAFE_URL . 'widgets/fx-slider/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-fx-slider-script', AAFE_URL . 'widgets/fx-slider/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // glass-showcase wp_register_style( 'aafe-glass-showcase-style', AAFE_URL . 'widgets/glass-showcase/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-glass-showcase-script', AAFE_URL . 'widgets/glass-showcase/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // kinetic-cylinder-gallery wp_register_style( 'aafe-kinetic-cylinder-gallery-style', AAFE_URL . 'widgets/kinetic-cylinder-gallery/assets/css/style.css', [], filemtime( AAFE_PATH . 'widgets/kinetic-cylinder-gallery/assets/css/style.css' ) ); wp_register_script( 'aafe-kinetic-cylinder-gallery-script', AAFE_URL . 'widgets/kinetic-cylinder-gallery/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], filemtime( AAFE_PATH . 'widgets/kinetic-cylinder-gallery/assets/js/script.js' ), array( 'in_footer' => true, 'strategy' => 'defer' ) ); // origin-button wp_register_style( 'aafe-origin-button-style', AAFE_URL . 'widgets/origin-button/assets/css/style.css', [], AAFE_VERSION ); wp_register_script( 'aafe-origin-button-script', AAFE_URL . 'widgets/origin-button/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); // scroll-cards wp_register_style( 'aafe-scroll-cards-style', AAFE_URL . 'widgets/scroll-cards/assets/css/style.css', [], file_exists( AAFE_PATH . 'widgets/scroll-cards/assets/css/style.css' ) ? (string) filemtime( AAFE_PATH . 'widgets/scroll-cards/assets/css/style.css' ) : AAFE_VERSION ); wp_register_script( 'aafe-scroll-cards-script', AAFE_URL . 'widgets/scroll-cards/assets/js/script.js', [ 'jquery', 'elementor-frontend' ], file_exists( AAFE_PATH . 'widgets/scroll-cards/assets/js/script.js' ) ? (string) filemtime( AAFE_PATH . 'widgets/scroll-cards/assets/js/script.js' ) : AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); } /** * Scan the /widgets/ directory, load each widget file, and register it with Elementor. */ private function load_and_register_widgets() { $widgets_path = AAFE_PATH . 'widgets/'; // If the widgets directory doesn't exist yet, bail early. if ( ! is_dir( $widgets_path ) ) { return; } // Get plugin settings to check for disabled widgets. $settings = get_option( 'aafe_settings', array() ); $disabled_widgets = isset( $settings['disabled_widgets'] ) && is_array( $settings['disabled_widgets'] ) ? $settings['disabled_widgets'] : array(); // Scan each subdirectory inside /widgets/. $widget_dirs = glob( $widgets_path . '*', GLOB_ONLYDIR ); if ( empty( $widget_dirs ) ) { return; } // Sort directories by creation time (newest first) usort( $widget_dirs, function( $a, $b ) { return filectime( $b ) - filectime( $a ); }); foreach ( $widget_dirs as $widget_dir ) { $widget_slug = basename( $widget_dir ); // Skip if this widget is individually disabled in the admin panel. if ( in_array( $widget_slug, $disabled_widgets, true ) ) { continue; } // The main widget PHP file must match the folder name. // e.g., /widgets/my-widget/my-widget.php $widget_file = $widget_dir . '/' . $widget_slug . '.php'; if ( ! file_exists( $widget_file ) ) { // Also try with "widget-" prefix pattern: /widgets/my-widget/widget-my-widget.php $widget_file = $widget_dir . '/widget-' . $widget_slug . '.php'; if ( ! file_exists( $widget_file ) ) { continue; } } try { // Require the widget class file. require_once $widget_file; // Derive the class name from the slug. // Convention: folder "my-widget" -> class "AddronixAddons\Widgets\My_Widget" // NOTE: ucfirst() does NOT capitalise digits, so "3d" stays "3d" -> class must be "3d" not "3D". $class_name = $this->slug_to_class_name( $widget_slug ); if ( ! class_exists( $class_name ) ) { continue; } // Register with Elementor. $this->elementor_widgets_manager->register( new $class_name() ); // Track registered widgets for the admin panel to display. $this->registered_widgets[] = array( 'slug' => $widget_slug, 'class' => $class_name, ); } catch ( \Throwable $e ) { // Track failed widgets and surface an admin notice so site owners can see the failure // even when WP_DEBUG is off (which is the WordPress-recommended default on production). self::$failed_widgets[] = $widget_slug; add_action( 'admin_notices', [ $this, 'render_widget_load_failures' ] ); if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( sprintf( '[AAFE] Widget "%s" failed to load: %s', $widget_slug, $e->getMessage() ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log } } } } /** * Convert a kebab-case folder slug to a fully-qualified class name. * * Examples: * 'my-widget' -> 'AddronixAddons\Widgets\My_Widget' * 'hero-banner' -> 'AddronixAddons\Widgets\Hero_Banner' * * @param string $slug Folder/widget slug. * @return string Full class name. */ private function slug_to_class_name( string $slug ): string { $parts = explode( '-', $slug ); $class_name = implode( '_', array_map( 'ucfirst', $parts ) ); return 'AddronixAddons\Widgets\\' . $class_name; } /** * Get the list of all registered widget slugs and class names. * Used by the admin panel to display the widget list. * * @return array */ public function get_registered_widgets(): array { return $this->registered_widgets; } /** * Render an admin notice listing widgets that failed to load. * Shown only to administrators to avoid confusing non-admin users. */ public function render_widget_load_failures() { if ( empty( self::$failed_widgets ) || ! current_user_can( 'manage_options' ) ) { return; } printf( '

%s

', esc_html( sprintf( /* translators: %s: comma-separated list of widget slugs */ __( 'Addronix Addons for Elementor: the following widgets failed to load and were skipped: %s. Enable WP_DEBUG to see the full error.', 'addronix-addons-for-elementor' ), implode( ', ', self::$failed_widgets ) ) ) ); } /** * Static helper: scan the widgets folder and return metadata for ALL * widgets (including disabled ones). Used purely for the admin panel display. * * @return array List of widget metadata arrays. */ public static function get_all_widget_metadata(): array { $widgets_path = dirname( __DIR__ ) . '/widgets/'; $all_widgets = array(); if ( ! is_dir( $widgets_path ) ) { return $all_widgets; } $settings = get_option( 'aafe_settings', array() ); $disabled_widgets = isset( $settings['disabled_widgets'] ) && is_array( $settings['disabled_widgets'] ) ? $settings['disabled_widgets'] : array(); $widget_dirs = glob( $widgets_path . '*', GLOB_ONLYDIR ); $dir_count = is_array( $widget_dirs ) ? count( $widget_dirs ) : 0; // Check transient cache first $transient_key = 'aafe_widget_metadata_v' . AAFE_VERSION; $cached_data = get_transient( $transient_key ); if ( false !== $cached_data && is_array( $cached_data ) && count( $cached_data ) === $dir_count ) { // Merge current enabled/disabled state with cached metadata foreach ( $cached_data as $slug => &$widget_data ) { $widget_data['enabled'] = ! in_array( $slug, $disabled_widgets, true ); } return $cached_data; } if ( empty( $widget_dirs ) ) { return $all_widgets; } // Sort directories by creation time (newest first) usort( $widget_dirs, function( $a, $b ) { return filectime( $b ) - filectime( $a ); }); foreach ( $widget_dirs as $widget_dir ) { $widget_slug = basename( $widget_dir ); $widget_file = $widget_dir . '/' . $widget_slug . '.php'; if ( ! file_exists( $widget_file ) ) { $widget_file = $widget_dir . '/widget-' . $widget_slug . '.php'; if ( ! file_exists( $widget_file ) ) { continue; } } // Read file content and extract class name and title using simple string matching $file_content = file_get_contents( $widget_file ); $class_name = ''; if ( preg_match( '/class\s+([a-zA-Z0-9_]+)\s+extends/i', $file_content, $matches ) ) { $class_name = $matches[1]; } $title = $class_name; if ( preg_match( '/public\s+function\s+get_title\(\).*?return\s+.*?(?:__|_e|_x)\s*\(\s*[\'"]([^\'"]+)[\'"]/is', $file_content, $matches ) ) { $title = $matches[1]; } $icon = 'eicon-code'; if ( preg_match( '/public\s+function\s+get_icon\(\)(?:\s*:\s*string)?\s*\{.*?return\s+[\'"]([^\'"]+)[\'"]/is', $file_content, $matches ) ) { $icon = $matches[1]; } $all_widgets[ $widget_slug ] = array( 'slug' => $widget_slug, 'class' => $class_name, 'title' => $title, 'icon' => $icon, 'enabled' => ! in_array( $widget_slug, $disabled_widgets, true ), ); } // Cache for 7 days (version change will invalidate it via the transient key) set_transient( $transient_key, $all_widgets, 7 * DAY_IN_SECONDS ); return $all_widgets; } }