getMessage() ]; } if ( ! empty( $api_mismatches ) ) { self::$atomic_api_mismatches = $api_mismatches; add_action( 'admin_notices', [ __CLASS__, 'render_atomic_api_mismatch_notice' ] ); return; } try { $available = self::is_atomic_editor_available(); } catch ( \Throwable $e ) { $available = false; } if ( ! $available ) { add_action( 'admin_notices', [ __CLASS__, 'render_atomic_disabled_notice' ] ); } } /** * Every class from Elementor's beta `Elementor\Modules\AtomicWidgets\...` * namespace that this plugin actually calls. Checked individually (not * just the base class) because a beta module can rename or move any one * of these in a point release without touching the others. * * @return string[] */ public static function required_atomic_widgets_classes(): array { return [ '\Elementor\Modules\AtomicWidgets\Elements\Base\Atomic_Widget_Base', '\Elementor\Modules\AtomicWidgets\Controls\Section', '\Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control', '\Elementor\Modules\AtomicWidgets\Controls\Types\Number_Control', '\Elementor\Modules\AtomicWidgets\Controls\Types\Switch_Control', '\Elementor\Modules\AtomicWidgets\Controls\Types\Text_Control', '\Elementor\Modules\AtomicWidgets\Controls\Types\Date_Range_Control', // NOTE: Classes_Control (per-addon CSS class fields, test pass on // Product Card) is deliberately NOT listed here. It's used // defensively via class_exists() at the call site instead, so // its absence degrades that one feature rather than blocking // the whole plugin the way a missing required class would. '\Elementor\Modules\AtomicWidgets\PropTypes\Classes_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Attributes_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Date_Range_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Size_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Background_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Dimensions_Prop_Type', '\Elementor\Modules\AtomicWidgets\Styles\Style_Definition', '\Elementor\Modules\AtomicWidgets\Styles\Style_Variant', '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type', '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Boolean_Prop_Type', '\Elementor\Modules\AtomicWidgets\Styles\Style_Definition', '\Elementor\Modules\AtomicWidgets\Styles\Style_Variant', ]; } /** * Checks that every class above exists AND still exposes the specific * methods this plugin calls on it (a rename is one risk; a changed * method signature on an otherwise-unchanged class is another). * Returns a list of problems — empty array means "safe to register". * * Every check below is individually wrapped in try/catch. This matters * because class_exists() does NOT fail gracefully here: if one of * Elementor's own beta files has a broken `extends` reference (e.g. its * atomic-widget-base.php pointing at a Widget_Base class that doesn't * exist in a given build), PHP throws a catchable \Error the moment * autoloading is triggered — it does not just return false. Without a * try/catch around each call, that \Error propagates as an uncaught * fatal and takes down the whole page. * * @return string[] */ public static function get_atomic_widgets_api_mismatches(): array { $problems = []; foreach ( self::required_atomic_widgets_classes() as $class ) { try { if ( ! class_exists( $class ) ) { $problems[] = "Missing class: {$class}"; } } catch ( \Throwable $e ) { $problems[] = "Class failed to load: {$class} ({$e->getMessage()})"; } } if ( ! empty( $problems ) ) { // No point checking methods on classes that didn't load cleanly. return $problems; } $method_checks = [ [ '\Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control', 'bind_to' ], [ '\Elementor\Modules\AtomicWidgets\Controls\Types\Select_Control', 'set_options' ], [ '\Elementor\Modules\AtomicWidgets\Controls\Section', 'make' ], [ '\Elementor\Modules\AtomicWidgets\Controls\Section', 'set_items' ], [ '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type', 'make' ], [ '\Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type', 'enum' ], ]; foreach ( $method_checks as [ $class, $method ] ) { try { if ( ! method_exists( $class, $method ) ) { $problems[] = "Missing method: {$class}::{$method}()"; } } catch ( \Throwable $e ) { $problems[] = "Method check failed: {$class}::{$method}() ({$e->getMessage()})"; } } return $problems; } /** * Whether Elementor's v4 Atomic Widgets engine is actually present and * turned on (it ships as a hidden, opt-in "experiment" in 4.0), *and* * its internal API still looks the way this plugin expects it to. */ public static function is_atomic_editor_available() { if ( ! empty( self::get_atomic_widgets_api_mismatches() ) ) { return false; } if ( class_exists( '\Elementor\Plugin' ) && isset( \Elementor\Plugin::$instance->experiments ) ) { // The feature flag Elementor itself registers for Atomic Widgets. if ( method_exists( \Elementor\Plugin::$instance->experiments, 'is_feature_active' ) && ! \Elementor\Plugin::$instance->experiments->is_feature_active( 'e_atomic_elements' ) ) { return false; } } return true; } public static function render_notices() { if ( empty( self::$errors ) || ! current_user_can( 'activate_plugins' ) ) { return; } echo '
Atomora:
Atomora: ' . esc_html__( 'Elementor\'s Atomic Editor (V4) is installed but not yet enabled on this site. Turn it on from Elementor → Editor → Settings → "Atomic Editor" (or the Features/Experiments panel), then reload this page. Atomora widgets will stay hidden until it is enabled.', 'atomora' ) . '
Atomora: ' . esc_html__( 'Elementor updated its Atomic Widgets engine and part of its internal structure no longer matches what this plugin expects. Atomora has safely disabled its widgets rather than risk breaking your site — nothing on your existing pages was touched.', 'atomora' ) . '
' . esc_html__( 'Details (for support / debugging):', 'atomora' ) . '
' . esc_html( $problem ) . '