is_compatible() ) { // AAFE FIX: add version compatibility check return; } $this->includes(); $this->register_hooks(); } /** * Check compatibility. */ public function is_compatible() { // AAFE FIX: check version compatibility method // Check if Elementor installed and activated if ( ! did_action( 'elementor/loaded' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_missing_main_plugin' ] ); return false; } // Check Elementor version if ( ! version_compare( ELEMENTOR_VERSION, self::MINIMUM_ELEMENTOR_VERSION, '>=' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_minimum_elementor_version' ] ); return false; } // Check PHP version if ( version_compare( PHP_VERSION, self::MINIMUM_PHP_VERSION, '<' ) ) { add_action( 'admin_notices', [ $this, 'admin_notice_minimum_php_version' ] ); return false; } return true; } public function admin_notice_missing_main_plugin() { // AAFE FIX: admin notice method if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking activate param, no state change. /* translators: 1: plugin name, 2: required plugin name */ $message = sprintf( esc_html__( '"%1$s" requires "%2$s" to be installed and activated.', 'addronix-addons-for-elementor' ), 'Addronix Addons for Elementor', 'Elementor' ); printf( '

%1$s

', $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Message is already escaped via esc_html__. } public function admin_notice_minimum_elementor_version() { // AAFE FIX: admin notice method if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking activate param, no state change. /* translators: 1: plugin name, 2: required plugin name, 3: required version */ $message = sprintf( esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'addronix-addons-for-elementor' ), 'Addronix Addons for Elementor', 'Elementor', self::MINIMUM_ELEMENTOR_VERSION ); printf( '

%1$s

', $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Message is already escaped via esc_html__. } public function admin_notice_minimum_php_version() { // AAFE FIX: admin notice method if ( isset( $_GET['activate'] ) ) unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking activate param, no state change. /* translators: 1: plugin name, 2: required plugin name, 3: required version */ $message = sprintf( esc_html__( '"%1$s" requires "%2$s" version %3$s or greater.', 'addronix-addons-for-elementor' ), 'Addronix Addons for Elementor', 'PHP', self::MINIMUM_PHP_VERSION ); printf( '

%1$s

', $message ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Message is already escaped via esc_html__. } /** * Prevent cloning of the singleton. */ private function __clone() {} /** * Prevent unserializing of the singleton. */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, esc_html__( 'Unserializing instances of this class is forbidden.', 'addronix-addons-for-elementor' ), '1.0.8' ); } /** * Require all dependency files. */ private function includes() { require_once AAFE_PATH . 'includes/class-aafe-template.php'; // Unified CPT (must load first) require_once AAFE_PATH . 'includes/class-widgets-manager.php'; require_once AAFE_PATH . 'includes/class-display-conditions.php'; // class-custom-code.php removed: per-element Custom CSS injection is not permitted on WordPress.org require_once AAFE_PATH . 'includes/class-header-footer.php'; require_once AAFE_PATH . 'includes/class-advanced-backgrounds.php'; require_once AAFE_PATH . 'includes/class-loop-builder.php'; require_once AAFE_PATH . 'includes/class-mega-menu.php'; require_once AAFE_PATH . 'includes/class-menu-badges.php'; // AAFE FIX BUG#17: added require here require_once AAFE_PATH . 'includes/class-sticky-elements.php'; require_once AAFE_PATH . 'includes/class-dynamic-tags.php'; // Only load the admin class in the admin context. if ( is_admin() ) { require_once AAFE_PATH . 'admin/class-admin.php'; } } /** * Register all WordPress / Elementor hooks. */ private function register_hooks() { // Register custom Elementor widget category. add_action( 'elementor/elements/categories_registered', array( $this, 'register_widget_categories' ) ); // Initialize the widgets manager after Elementor widgets are registered. add_action( 'elementor/widgets/register', array( $this, 'init_widgets_manager' ) ); // Register custom controls. add_action( 'elementor/controls/register', array( $this, 'register_controls' ) ); // Register post dynamic tags (Post Title, Featured Image, ...) used by Loop Item templates. add_action( 'elementor/dynamic_tags/register', array( '\AddronixAddons\Dynamic_Tags', 'register' ) ); // Initialize display conditions extension. add_action( 'elementor/init', array( $this, 'init_display_conditions' ) ); // Initialize all advanced extensions. add_action( 'elementor/init', array( $this, 'init_extensions' ) ); // Auto-enable all AAFE CPTs in Elementor's supported post types list. add_action( 'elementor/init', array( $this, 'register_elementor_cpt_support' ) ); // Enqueue global frontend assets. add_action( 'elementor/frontend/after_enqueue_styles', array( $this, 'enqueue_frontend_assets' ) ); // AAFE FIX: move to elementor/frontend/after_enqueue_styles // Initialize admin panel. if ( is_admin() ) { new \AddronixAddons\Admin\Admin(); } } /** * Automatically register the unified AAFE CPT with Elementor * so it appears as ONE checkbox — "Addronix Addons Items" — in * Elementor Settings → Post Types. One check enables everything. */ public function register_elementor_cpt_support() { // NOTE: 'elementor_cpt_support' is Elementor's own built-in option key (not a custom prefix). // We intentionally read and update this option to register our CPT with Elementor's builder. // This is the standard WordPress approach for adding CPT support to Elementor. $cpt_support = get_option( 'elementor_cpt_support', [ 'page', 'post' ] ); if ( ! in_array( \AddronixAddons\AAFE_Template::CPT, $cpt_support, true ) ) { $cpt_support[] = \AddronixAddons\AAFE_Template::CPT; update_option( 'elementor_cpt_support', $cpt_support ); } } /** * Register the "Addronix Addons for Elementor" widget category in Elementor. * * @param \Elementor\Elements_Manager $elements_manager Elementor elements manager. */ public function register_widget_categories( $elements_manager ) { $elements_manager->add_category( 'addronix-addons-for-elementor', array( 'title' => esc_html__( 'Addronix Addons for Elementor', 'addronix-addons-for-elementor' ), 'icon' => 'eicon-flash', ) ); // Unlock & promote "Site" (theme-elements) category ONLY when editing or creating a Header or Footer template. // On normal pages/posts, leave categories in their standard order. if ( ! $this->is_editing_header_or_footer() ) { return; } // Unlock & promote "Site" (theme-elements) category so Site widgets appear at top for headers/footers try { $ref = new \ReflectionClass( $elements_manager ); if ( $ref->hasProperty( 'categories' ) ) { $prop = $ref->getProperty( 'categories' ); $prop->setAccessible( true ); $cats = $prop->getValue( $elements_manager ); if ( is_array( $cats ) && isset( $cats['theme-elements'] ) ) { $site_cat = $cats['theme-elements']; $site_cat['active'] = true; unset( $site_cat['promotion'] ); unset( $cats['theme-elements'] ); $new_cats = []; if ( isset( $cats['favorites'] ) ) { $new_cats['favorites'] = $cats['favorites']; unset( $cats['favorites'] ); } $new_cats['theme-elements'] = $site_cat; $new_cats = array_merge( $new_cats, $cats ); $prop->setValue( $elements_manager, $new_cats ); } } } catch ( \Throwable $e ) { // Fail-safe silently } } /** * Check whether the current Elementor editor session is editing a Header or Footer template. * * @return bool True if editing a Header or Footer, false otherwise. */ private function is_editing_header_or_footer() { $post_id = 0; // 1. Direct query parameters in admin / preview if ( ! empty( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_GET['post'] ); } elseif ( ! empty( $_GET['elementor-preview'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_GET['elementor-preview'] ); } elseif ( ! empty( $_REQUEST['editor_post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_REQUEST['editor_post_id'] ); } elseif ( ! empty( $_REQUEST['post_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint( $_REQUEST['post_id'] ); } // 2. Elementor Editor API fallback if ( ! $post_id && class_exists( '\Elementor\Plugin' ) ) { if ( isset( \Elementor\Plugin::$instance->editor ) && method_exists( \Elementor\Plugin::$instance->editor, 'get_post_id' ) ) { $post_id = \Elementor\Plugin::$instance->editor->get_post_id(); } if ( ! $post_id && isset( \Elementor\Plugin::$instance->documents ) && method_exists( \Elementor\Plugin::$instance->documents, 'get_current' ) ) { $doc = \Elementor\Plugin::$instance->documents->get_current(); if ( $doc && method_exists( $doc, 'get_main_id' ) ) { $post_id = $doc->get_main_id(); } } } // 3. New post screen check before post ID is assigned if ( ! $post_id ) { if ( isset( $_GET['post_type'] ) && 'aafe_item' === $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $type = isset( $_GET['aafe_type'] ) ? sanitize_key( wp_unslash( $_GET['aafe_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended return in_array( $type, [ 'header', 'footer', 'header-footer' ], true ); } return false; } // Check post type of the post being edited $post_type = get_post_type( $post_id ); // AAFE unified template CPT check if ( 'aafe_item' === $post_type ) { $type = get_post_meta( $post_id, \AddronixAddons\AAFE_Template::META_TYPE, true ); if ( ! $type ) { $type = get_post_meta( $post_id, '_aafe_hf_type', true ); } // If meta is not saved yet, check URL parameter if ( ! $type && isset( $_GET['aafe_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $type = sanitize_key( wp_unslash( $_GET['aafe_type'] ) ); } // Exclude non-header/footer types like loop and mega-menu if ( in_array( $type, [ 'loop', 'mega-menu' ], true ) ) { return false; } // If empty or explicitly header/footer/header-footer, it belongs to header-footer builder return true; } // Check Elementor Pro Theme Builder document type if active if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->documents ) ) { $doc = \Elementor\Plugin::$instance->documents->get( $post_id ); if ( $doc && method_exists( $doc, 'get_name' ) && in_array( $doc->get_name(), [ 'header', 'footer' ], true ) ) { return true; } } return false; } /** * Register custom Elementor controls. * * @param \Elementor\Controls_Manager $controls_manager Elementor controls manager. */ public function register_controls( $controls_manager ) { require_once AAFE_PATH . 'includes/controls/class-control-display-conditions.php'; $controls_manager->register( new \AddronixAddons\Controls\Control_Display_Conditions() ); } private $widgets_registered = false; /** * Widgets manager instance (declared to avoid PHP 8.2+ dynamic property deprecation). * * @var Widgets_Manager|null */ private $widgets_manager = null; /** * Initialize the Widgets Manager, which scans and registers all widgets. * * @param \Elementor\Widgets_Manager $widgets_manager Elementor widgets manager. */ public function init_widgets_manager( $widgets_manager ) { if ( $this->widgets_registered ) { return; } $this->widgets_registered = true; // Check if plugin is globally enabled. $settings = get_option( 'aafe_settings', array() ); $plugin_enabled = isset( $settings['plugin_enabled'] ) ? $settings['plugin_enabled'] : 'yes'; if ( 'yes' !== $plugin_enabled ) { return; } // AAFE FIX BUG#14: Removed duplicate require_once — file is already loaded in includes(). $this->widgets_manager = new Widgets_Manager( $widgets_manager ); } /** * Initialize Display Conditions Extension. */ public function init_display_conditions() { $settings = get_option( 'aafe_settings', array() ); $plugin_enabled = isset( $settings['plugin_enabled'] ) ? $settings['plugin_enabled'] : 'yes'; if ( 'yes' !== $plugin_enabled ) { return; } // AAFE FIX BUG#2: Respect disabled_extensions setting for Display Conditions. $disabled = isset( $settings['disabled_extensions'] ) && is_array( $settings['disabled_extensions'] ) ? $settings['disabled_extensions'] : array(); if ( in_array( 'ext-display-conditions', $disabled, true ) ) { return; } \AddronixAddons\Display_Conditions::instance(); } /** * Initialize all advanced extensions (Custom Code, Header/Footer, etc.). */ public function init_extensions() { $settings = get_option( 'aafe_settings', array() ); $plugin_enabled = isset( $settings['plugin_enabled'] ) ? $settings['plugin_enabled'] : 'yes'; if ( 'yes' !== $plugin_enabled ) { return; } // AAFE FIX BUG#2: Read disabled_extensions and conditionally init each extension. $disabled = isset( $settings['disabled_extensions'] ) && is_array( $settings['disabled_extensions'] ) ? $settings['disabled_extensions'] : array(); \AddronixAddons\AAFE_Template::instance(); // Unified CPT — must init before sub-classes // Custom_Code::instance() removed — arbitrary CSS injection not permitted on WordPress.org if ( ! in_array( 'ext-hf-builder', $disabled, true ) ) { \AddronixAddons\Header_Footer::instance(); } if ( ! in_array( 'ext-adv-backgrounds', $disabled, true ) ) { \AddronixAddons\Advanced_Backgrounds::instance(); } if ( ! in_array( 'ext-loop-builder', $disabled, true ) ) { \AddronixAddons\Loop_Builder::instance(); } if ( ! in_array( 'ext-mega-menu', $disabled, true ) ) { \AddronixAddons\Mega_Menu::instance(); } // AAFE FIX BUG#17: Menu_Badges moved here from class-menu-badges.php self-init. if ( ! in_array( 'ext-menu-badges', $disabled, true ) ) { \AddronixAddons\Menu_Badges::instance(); } if ( ! in_array( 'ext-sticky-elements', $disabled, true ) ) { \AddronixAddons\Sticky_Elements::instance(); } } /** * Enqueue shared frontend CSS/JS (only when needed on the frontend). */ public function enqueue_frontend_assets() { // AAFE FIX BUG#3: Respect performance_mode — only load on Elementor-built pages. $settings = get_option( 'aafe_settings', array() ); $perf_mode = isset( $settings['performance_mode'] ) ? $settings['performance_mode'] : 'no'; if ( 'yes' === $perf_mode ) { $is_preview = class_exists( '\Elementor\Plugin' ) && \Elementor\Plugin::$instance->preview->is_preview_mode(); $is_elementor_page = class_exists( '\Elementor\Plugin' ) && \Elementor\Plugin::$instance->db->is_built_with_elementor( get_the_ID() ); if ( ! $is_preview && ! $is_elementor_page ) { return; } } // Enqueue the shared frontend stylesheet. wp_enqueue_style( 'aafe-frontend', AAFE_URL . 'assets/css/frontend.css', array(), AAFE_VERSION ); // Enqueue shared frontend script. wp_enqueue_script( 'aafe-frontend', AAFE_URL . 'assets/js/frontend.js', array( 'jquery' ), AAFE_VERSION, array( 'in_footer' => true, 'strategy' => 'defer' ) ); } }