register_variants_experiment(); $this->register_component_post_type(); add_filter( 'elementor/editor/v2/packages', fn ( $packages ) => $this->add_packages( $packages ) ); add_filter( 'elementor/atomic-widgets/props-schema', fn ( $schema ) => $this->modify_props_schema( $schema ) ); add_action( 'elementor/documents/register', fn ( $documents_manager ) => $this->register_document_type( $documents_manager ) ); add_action( 'elementor/document/before_save', fn( Document $document, array $data ) => $this->validate_circular_dependencies( $document, $data ), 10, 2 ); add_action( 'elementor/document/after_save', fn( Document $document, array $data ) => $this->set_component_overridable_props( $document, $data ), 10, 2 ); if ( self::is_variants_experiment_active() ) { add_action( 'elementor/document/after_save', fn( Document $document, array $data ) => $this->set_component_variants( $document, $data ), self::SAVE_VARIANTS_PRIORITY, 2 ); add_filter( 'elementor/global_classes/extract_class_ids_from_post', fn( array $ids, $post_id ) => $this->add_variant_class_ids( $ids, $post_id ), 10, 2 ); } add_filter( 'elementor/global_classes/additional_post_types', fn( $post_types ) => array_merge( $post_types, [ Component_Document::TYPE ] ) ); add_filter( 'elementor/utils/find_element_recursive/inner_elements', fn( array $inner_elements, array $element_data ) => $this->get_inner_elements_for_search( $inner_elements, $element_data ), 10, 2 ); add_action( 'elementor/atomic-widgets/settings/transformers/register', fn ( $transformers ) => $this->register_settings_transformers( $transformers ) ); add_action( 'elementor/document/after_migrate', fn( Document $document, array $data ) => $this->after_component_migrate( $document, $data ), 10, 2 ); add_filter( 'elementor/atomic-widgets/llm-json-schema', fn( array $schema ) => ( new Overridable_LLM_Filter() )->apply( $schema ) ); ( Component_Lock_Manager::get_instance()->register_hooks() ); ( new Component_Styles() )->register_hooks(); ( new Components_REST_API() )->register_hooks(); } public static function is_experiment_active() { return Plugin::$instance->experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } public static function is_variants_experiment_active(): bool { return Plugin::$instance->experiments->is_feature_active( self::EXPERIMENT_VARIANTS_NAME ); } /** * Dev-only gate that keeps per-instance Component Variants off trunk while the feature ships * across several tickets. Hidden experiments cannot declare dependencies on other experiments, * so activation is checked manually in the constructor after the parent atomic-elements gate. */ private function register_variants_experiment() { Plugin::$instance->experiments->add_feature( [ 'name' => self::EXPERIMENT_VARIANTS_NAME, 'title' => esc_html__( 'Component Variants', 'elementor' ), 'description' => esc_html__( 'Enable per-instance class variants on components.', 'elementor' ), 'hidden' => true, 'default' => Experiments_Manager::STATE_INACTIVE, 'release_status' => Experiments_Manager::RELEASE_STATUS_DEV, ] ); } public function get_widgets() { return [ 'Component_Instance', ]; } private function add_packages( $packages ) { return array_merge( $packages, self::PACKAGES ); } private function modify_props_schema( array $schema ) { return Overridable_Schema_Extender::make()->get_extended_schema( $schema ); } private function register_component_post_type() { register_post_type( Component_Document::TYPE, [ 'label' => Component_Document::get_title(), 'labels' => Component_Document::get_labels(), 'public' => false, 'supports' => Component_Document::get_supported_features(), ] ); } private function register_document_type( $documents_manager ) { $documents_manager->register_document_type( Component_Document::TYPE, Component_Document::get_class_full_name() ); } private function validate_circular_dependencies( Document $document, array $data ) { if ( ! $document instanceof Component_Document ) { return; } if ( ! isset( $data['elements'] ) ) { return; } $component_id = $document->get_main_id(); $elements = $data['elements']; $result = Circular_Dependency_Validator::make()->validate( $component_id, $elements ); if ( ! $result['success'] ) { throw new \Exception( esc_html__( "Can't add this component - components that contain each other can't be nested.", 'elementor' ) ); } } private function set_component_overridable_props( Document $document, array $data ) { if ( ! isset( $data['settings'] ) ) { return; } if ( ( ! $document instanceof Component_Document ) || ( ! isset( $data['settings']['overridable_props'] ) ) ) { return; } if ( ! Components_Access_Controller::can_edit() ) { throw new \Exception( esc_html__( 'You do not have permission to edit component source.', 'elementor' ) ); } /* @var Component_Document $document */ $result = $document->update_overridable_props( $data['settings']['overridable_props'] ); if ( ! $result->is_valid() ) { throw new \Exception( esc_html( 'Settings validation failed for component overridable props: ' . $result->errors()->to_string() ) ); } } private function set_component_variants( Document $document, array $data ) { if ( ! isset( $data['settings'] ) ) { return; } if ( ( ! $document instanceof Component_Document ) || ( ! isset( $data['settings']['variants'] ) ) ) { return; } if ( ! Components_Access_Controller::can_edit() ) { throw new \Exception( esc_html__( 'You do not have permission to edit component source.', 'elementor' ) ); } /* @var Component_Document $document */ $result = $document->update_variants( $data['settings']['variants'] ); if ( ! $result->is_valid() ) { throw new \Exception( esc_html( 'Settings validation failed for component variants: ' . $result->errors()->to_string() ) ); } } private function add_variant_class_ids( array $ids, $post_id ): array { $document = Plugin::$instance->documents->get( (int) $post_id ); if ( ! $document instanceof Component_Document ) { return $ids; } return array_merge( $ids, Component_Variant_Class_Collector::collect( $document->get_variants() ) ); } private function register_settings_transformers( Transformers_Registry $transformers ) { $transformers->register( Component_Instance_Prop_Type::get_key(), new Component_Instance_Transformer() ); $transformers->register( Overridable_Prop_Type::get_key(), new Overridable_Transformer() ); $transformers->register( Override_Prop_Type::get_key(), new Override_Transformer() ); } private function after_component_migrate( Document $document, array $data ) { if ( ! $document instanceof Component_Document ) { return; } $document->align_overridable_props_with_elements(); } private function get_inner_elements_for_search( array $inner_elements, array $element_data ): array { if ( ! $this->is_component_instance( $element_data ) ) { return $inner_elements; } $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element_instance instanceof Component_Instance ) { return []; } return $element_instance->get_inner_elements_data_for_search(); } private function is_component_instance( array $element_data ): bool { return isset( $element_data['elType'], $element_data['widgetType'] ) && 'widget' === $element_data['elType'] && Component_Instance::get_element_type() === $element_data['widgetType']; } }