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 );
}
}