PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.1
Elementor Website Builder – more than just a page builder v4.3.1
4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 All 454 releases
elementor / modules / mcp / abilities / appliers / element-config-applier.php

element-config-applier.php in Elementor Website Builder – more than just a page builder 4.3.1, at modules/mcp/abilities/appliers/element-config-applier.php

249 lines 7.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Mcp\Abilities\Appliers;
4
5 use Elementor\Core\Base\Document;
6 use Elementor\Modules\AtomicWidgets\Parsers\Props_Parser;
7 use Elementor\Modules\AtomicWidgets\PlainResolvers\Plain_Values_Resolver;
8 use Elementor\Modules\AtomicWidgets\PropTypes\Contracts\Prop_Type;
9 use Elementor\Modules\Components\Components_Repository;
10 use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Dynamic_Hoister;
11 use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Non_Style_Allowlist;
12 use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Settings_Validator;
13 use Elementor\Modules\Mcp\Abilities\Build_Composition\Widget_Type_Resolver;
14 use Elementor\Modules\Mcp\Abilities\Prop_Canonicalizer;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 class Element_Config_Applier {
21
22 const COMPONENT_INSTANCE_WIDGET_TYPE = 'e-component';
23
24 private Widget_Type_Resolver $type_resolver;
25 private Plain_Values_Resolver $plain_values_resolver;
26 private ?V3_Dynamic_Hoister $v3_dynamic_hoister;
27
28 public function __construct(
29 Widget_Type_Resolver $type_resolver,
30 Plain_Values_Resolver $plain_values_resolver,
31 ?V3_Dynamic_Hoister $v3_dynamic_hoister = null
32 ) {
33 $this->type_resolver = $type_resolver;
34 $this->plain_values_resolver = $plain_values_resolver;
35 $this->v3_dynamic_hoister = $v3_dynamic_hoister;
36 }
37
38 /**
39 * @param array<string, array&> $config_id_index Index of subtree refs.
40 * @param array<string, array<string, mixed>> $element_config Per-config-id settings.
41 * @param array<string, array> $widget_configs Resolved type configs.
42 * @param Document|null $document Target document, when one already exists.
43 *
44 * @return array{ error: ?\WP_Error, warnings: string[] }
45 */
46 public function apply( array &$config_id_index, array $element_config, array $widget_configs, ?Document $document = null ): array {
47 $errors = [];
48 $warnings = [];
49 $warning_codes = [];
50 $component_entries = [];
51
52 foreach ( $element_config as $config_id => $settings ) {
53 if ( ! isset( $config_id_index[ $config_id ] ) || ! is_array( $settings ) ) {
54 continue;
55 }
56
57 $node = &$config_id_index[ $config_id ];
58 $tag = $node['widgetType'] ?? $node['elType'] ?? null;
59
60 if ( self::COMPONENT_INSTANCE_WIDGET_TYPE === $tag ) {
61 $component_entries[ $config_id ] = $settings;
62 continue;
63 }
64
65 if ( V3_Node_Bridge::is_v3_node( $node ) ) {
66 $widget_type = (string) $tag;
67 $filter = V3_Non_Style_Allowlist::filter( $widget_type, $settings );
68 if ( $filter['error'] ) {
69 $errors[] = sprintf( '[%s] %s', $config_id, $filter['error']->get_error_message() );
70 continue;
71 }
72
73 $widget_config = is_array( $widget_configs[ $widget_type ] ?? null ) ? $widget_configs[ $widget_type ] : [];
74 $controls = is_array( $widget_config['controls'] ?? null ) ? $widget_config['controls'] : [];
75
76 $hoist_outcome = $this->get_v3_dynamic_hoister()->hoist( $widget_type, $filter['allowed'], $controls );
77
78 foreach ( $hoist_outcome['errors'] as $error_message ) {
79 $errors[] = sprintf( '[%s] %s', $config_id, $error_message );
80 }
81
82 $shape = V3_Settings_Validator::validate_shape( $widget_type, $hoist_outcome['primitives'], $widget_config );
83
84 if ( ! empty( $shape['valid'] ) ) {
85 $node['settings'] = $this->merge_with_clears( $node['settings'] ?? [], $shape['valid'] );
86 }
87
88 if ( $shape['error'] ) {
89 $errors[] = sprintf( '[%s] %s', $config_id, $shape['error']->get_error_message() );
90 }
91
92 if ( ! empty( $hoist_outcome['shortcodes'] ) ) {
93 $existing = is_array( $node['settings']['__dynamic__'] ?? null ) ? $node['settings']['__dynamic__'] : [];
94 $node['settings']['__dynamic__'] = array_merge( $existing, $hoist_outcome['shortcodes'] );
95 }
96
97 continue;
98 }
99
100 $schema = $this->type_resolver->get_props_schema( $tag, $widget_configs );
101
102 if ( ! $schema ) {
103 $node['settings'] = $this->merge_with_clears( $node['settings'] ?? [], $settings );
104 continue;
105 }
106
107 $outcome = $this->resolve_settings_against_schema( $settings, $schema, $tag, $config_id, $errors, $warnings, $warning_codes );
108
109 $node['settings'] = array_merge( $node['settings'] ?? [], $outcome['resolved'] );
110
111 foreach ( $outcome['cleared'] as $cleared_key ) {
112 unset( $node['settings'][ $cleared_key ] );
113 }
114
115 $validation_error = $this->validate_settings( $node['settings'], $schema );
116 if ( $validation_error ) {
117 $errors[] = sprintf(
118 '[%s] Settings validation failed on element type "%s": %s. See elementor://widgets/schema/%s.',
119 $config_id,
120 $tag,
121 $validation_error,
122 $tag
123 );
124 }
125 }
126 unset( $node );
127
128 $component_error = empty( $component_entries )
129 ? null
130 : $this->apply_component_entries( $config_id_index, $component_entries, $document );
131
132 return [
133 'error' => $this->combine_errors( $errors, $component_error ),
134 'warnings' => $warnings,
135 'warning_codes' => array_values( array_unique( $warning_codes ) ),
136 ];
137 }
138
139 private function combine_errors( array $settings_errors, ?\WP_Error $component_error ): ?\WP_Error {
140 if ( empty( $settings_errors ) ) {
141 return $component_error;
142 }
143
144 if ( $component_error ) {
145 $settings_errors[] = $component_error->get_error_message();
146 }
147
148 return new \WP_Error(
149 'elementor_invalid_settings',
150 implode( ' ', $settings_errors ),
151 [ 'status' => \WP_Http::BAD_REQUEST ]
152 );
153 }
154
155 private function apply_component_entries( array &$config_id_index, array $component_entries, ?Document $document ): ?\WP_Error {
156 return $this->create_component_applier()->apply( $config_id_index, $component_entries, $document );
157 }
158
159 private function create_component_applier(): Component_Instance_Applier {
160 return new Component_Instance_Applier( new Components_Repository(), $this->plain_values_resolver );
161 }
162
163 private function resolve_settings_against_schema(
164 array $settings,
165 array $schema,
166 string $element_type,
167 string $config_id,
168 array &$errors,
169 array &$warnings,
170 array &$warning_codes = []
171 ): array {
172 $alias_map = Prop_Canonicalizer::build_alias_map( $schema );
173 $resolved = [];
174 $cleared = [];
175
176 foreach ( $settings as $name => $value ) {
177 $canonical = Prop_Canonicalizer::resolve_canonical_key( $schema, $name, $alias_map );
178
179 if ( null === $canonical ) {
180 $warnings[] = sprintf(
181 '[%s] Property "%s" is not supported on element type "%s" and was skipped.',
182 $config_id,
183 $name,
184 $element_type
185 );
186 $warning_codes[] = 'prop_not_supported';
187 continue;
188 }
189
190 if ( null === $value ) {
191 $cleared[] = $canonical;
192 continue;
193 }
194
195 $prop_type = $schema[ $canonical ] ?? null;
196
197 if ( ! $prop_type instanceof Prop_Type ) {
198 continue;
199 }
200
201 $resolved_value = $this->plain_values_resolver->resolve( $value, $prop_type );
202
203 if ( null === $resolved_value ) {
204 $errors[] = sprintf(
205 '[%s] Property "%s" on "%s" could not be resolved. See elementor://widgets/schema/%s.',
206 $config_id,
207 $canonical,
208 $element_type,
209 $element_type
210 );
211 continue;
212 }
213
214 $resolved[ $canonical ] = $resolved_value;
215 }
216
217 return [
218 'resolved' => $resolved,
219 'cleared' => $cleared,
220 ];
221 }
222
223 private function merge_with_clears( array $existing, array $incoming ): array {
224 $merged = $existing;
225 foreach ( $incoming as $key => $value ) {
226 if ( null === $value ) {
227 unset( $merged[ $key ] );
228 continue;
229 }
230 $merged[ $key ] = $value;
231 }
232 return $merged;
233 }
234
235 private function validate_settings( array $settings, array $schema ): ?string {
236 $result = Props_Parser::make( $schema )->parse( $settings );
237
238 return $result->is_valid() ? null : $result->errors()->to_string();
239 }
240
241 private function get_v3_dynamic_hoister(): V3_Dynamic_Hoister {
242 if ( null === $this->v3_dynamic_hoister ) {
243 $this->v3_dynamic_hoister = new V3_Dynamic_Hoister();
244 }
245
246 return $this->v3_dynamic_hoister;
247 }
248 }
249