parse( (string) ( $input['xml_structure'] ?? '' ) ); if ( is_wp_error( $dom ) ) { return $dom; } [ 'configs' => $widget_configs, 'unknown_tag_errors' => $unknown_widget_tag_errors, ] = $type_resolver->collect_referenced_widget_configs( $dom ); $validation_error = $this->validate_xml_before_apply( $dom, $xml_parser, $document_tree, $parent_id, $unknown_widget_tag_errors ); if ( $validation_error ) { return $validation_error; } $wrapping_result = $this->wrap_document_root_content( $dom, $widget_configs, $parent_id, $type_resolver, $xml_parser ); if ( is_wp_error( $wrapping_result ) ) { return $wrapping_result; } $widget_configs = $wrapping_result['widget_configs']; $child_type_errors = $type_resolver->collect_child_type_and_required_child_errors( $dom, $widget_configs ); if ( ! empty( $child_type_errors ) ) { return new \WP_Error( 'elementor_invalid_child_type', implode( ' ', $child_type_errors ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } $subtrees = $subtree_builder->build( $dom, $widget_configs ); if ( empty( $subtrees ) ) { return new \WP_Error( 'empty_composition', __( 'xml_structure did not contain any elements. Pass raw XML tags (e.g. ) — do not wrap the value in or other text-only content.', 'elementor' ), [ 'status' => \WP_Http::BAD_REQUEST ] ); } $index = $subtree_builder->index_by_config_id( $subtrees, $dom ); $variables_service = $this->create_variables_service(); $config_applier = new Element_Config_Applier( $type_resolver, $this->get_plain_values_resolver() ); $config_result = $config_applier->apply( $index, $this->as_map( $input['element_config'] ?? [] ), $widget_configs, $document ); if ( $config_result['error'] ) { return $config_result['error']; } $class_applier = new Class_Applier( $this->create_global_classes_repository() ); $class_error = $class_applier->apply( $index, $this->as_map( $input['classes'] ?? [] ) ); if ( $class_error ) { return $class_error; } $style_applier = new Style_Applier( $this->create_css_converter( $variables_service ), $this->get_active_breakpoints() ); $style_result = $style_applier->apply( $index, $this->as_map( $input['style'] ?? [] ), 'patch', $widget_configs ); if ( $style_result['error'] ) { return $style_result['error']; } $interactions_result = $this->apply_interactions( $index, $this->as_map( $input['interactions'] ?? [] ) ); if ( $interactions_result['error'] ) { return $interactions_result['error']; } return [ 'elements' => $subtrees, 'warnings' => array_merge( $wrapping_result['warnings'], $config_result['warnings'], $style_result['warnings'], $interactions_result['warnings'] ), 'warning_codes' => array_values( array_unique( array_merge( $wrapping_result['warning_codes'] ?? [], $config_result['warning_codes'] ?? [], $style_result['warning_codes'] ?? [], $interactions_result['warning_codes'] ?? [] ) ) ), 'dom' => $dom, 'xml_parser' => $xml_parser, ]; } /** * @param \DOMDocument $dom * @param Xml_Parser $xml_parser * @param array $document_tree * @param string $parent_id * @param string[] $unknown_widget_tag_errors * * @return \WP_Error|null */ private function validate_xml_before_apply( \DOMDocument $dom, Xml_Parser $xml_parser, array $document_tree, string $parent_id, array $unknown_widget_tag_errors ) { $errors = array_merge( $this->tag_errors( 'elementor_duplicate_configuration_id', $xml_parser->collect_duplicate_configuration_id_errors( $dom ) ), $this->tag_errors( 'elementor_invalid_form_structure', ( new Form_Structure_Validator( $xml_parser ) )->collect_errors( $dom, $document_tree, $parent_id ) ), $this->tag_errors( 'elementor_unknown_type', $unknown_widget_tag_errors ), ); if ( empty( $errors ) ) { return null; } $status = [ 'status' => \WP_Http::BAD_REQUEST ]; $joined = implode( ' ', array_column( $errors, 'message' ) ); $aggregated = new \WP_Error( $errors[0]['code'], $joined, $status ); foreach ( array_slice( $errors, 1 ) as $error ) { $aggregated->add( $error['code'], $error['message'], $status ); } return $aggregated; } /** * @param string $code Error code to tag each message with. * @param string[] $messages List of error messages. * * @return array */ private function tag_errors( string $code, array $messages ): array { return array_map( fn ( $message ) => [ 'code' => $code, 'message' => $message, ], $messages ); } private function wrap_document_root_content( \DOMDocument $dom, array $widget_configs, string $parent_id, Widget_Type_Resolver $type_resolver, Xml_Parser $xml_parser ) { if ( self::DEFAULT_PARENT_ID !== $parent_id ) { return [ 'widget_configs' => $widget_configs, 'warnings' => [], 'warning_codes' => [], ]; } $root = $xml_parser->get_root( $dom ); if ( ! $root ) { return [ 'widget_configs' => $widget_configs, 'warnings' => [], 'warning_codes' => [], ]; } $root_children = $xml_parser->get_child_elements( $root ); $has_widget = false; foreach ( $root_children as $child ) { $tag = $xml_parser->get_tag_name( $child ); $config = $widget_configs[ $tag ] ?? []; if ( 'widget' !== ( $config['elType'] ?? null ) ) { continue; } $has_widget = true; break; } if ( ! $has_widget ) { return [ 'widget_configs' => $widget_configs, 'warnings' => [], 'warning_codes' => [], ]; } $wrapper_config = $type_resolver->resolve_type_config( self::DOCUMENT_ROOT_WRAPPER ); if ( is_wp_error( $wrapper_config ) ) { return $wrapper_config; } $widget_configs[ self::DOCUMENT_ROOT_WRAPPER ] = $wrapper_config; $wrapper = $dom->createElement( self::DOCUMENT_ROOT_WRAPPER ); $root->appendChild( $wrapper ); foreach ( $root_children as $child ) { $wrapper->appendChild( $child ); } return [ 'widget_configs' => $widget_configs, 'warnings' => [ __( 'Direct document-root content was wrapped in an e-div-block element.', 'elementor' ) ], 'warning_codes' => [ 'root_auto_wrapped' ], ]; } private function as_map( $value ): array { if ( is_object( $value ) ) { $value = (array) $value; } return is_array( $value ) ? $value : []; } private function get_active_breakpoints(): array { return array_keys( Plugin::$instance->breakpoints->get_active_breakpoints() ); } private function create_css_converter( ?Variables_Service $variables_service ): Css_Converter { $variable_transformer = $variables_service ? new Variable_Prop_Value_Transformer( $variables_service ) : null; return new Css_Converter( Converter_Registry_Factory::create( $variables_service ), new Null_Failure_Reporter(), Expander_Registry_Factory::create( $variables_service ), $variable_transformer ); } private function create_variables_service(): ?Variables_Service { if ( ! $this->is_variables_active() ) { return null; } $kit = Plugin::$instance->kits_manager->get_active_kit(); if ( ! $kit ) { return null; } return new Variables_Service( new Variables_Repository( $kit ), new Batch_Processor() ); } private function get_plain_values_resolver(): Plain_Values_Resolver { return AtomicWidgetsModule::instance()->get_settings_plain_values_resolver(); } /** * @param array $index * @param array> $interactions * * @return array{error: \WP_Error|null, warnings: string[]} */ private function apply_interactions( array &$index, array $interactions ): array { if ( empty( $interactions ) ) { return [ 'error' => null, 'warnings' => [], 'warning_codes' => [], ]; } if ( ! Plugin::$instance->experiments->is_feature_active( Interactions_Module::EXPERIMENT_NAME ) ) { return [ 'error' => null, 'warnings' => [ __( 'Interactions experiment is not active. Interactions were not applied.', 'elementor' ) ], 'warning_codes' => [ 'interactions_experiment_off' ], ]; } $applier = new Interactions_Applier( $this->get_plain_values_resolver() ); return $applier->apply( $index, $interactions ) + [ 'warning_codes' => [] ]; } private function is_variables_active(): bool { $experiments = Plugin::$instance->experiments; return $experiments->is_feature_active( Variables_Module::EXPERIMENT_NAME ) && $experiments->is_feature_active( AtomicWidgetsModule::EXPERIMENT_NAME ); } private function create_global_classes_repository(): Global_Classes_Repository { $kit = Plugin::$instance->kits_manager->get_active_kit(); return Global_Classes_Repository::make( $kit ); } }