PluginProbe
Elementor Website Builder – more than just a page builder / trunk
Elementor Website Builder – more than just a page builder vtrunk
4.3.3 4.3.2 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 All 456 releases
← All changes | modules/mcp/abilities/appliers/style-applier.php +124 -18 4.3.0-beta1 → trunk View file →
@@ -2,12 +2,14 @@
2 2
3 3 namespace Elementor\Modules\Mcp\Abilities\Appliers;
4 4
5 5 use Elementor\Modules\AtomicWidgets\CssConverter\Css_Converter;
6 +use Elementor\Modules\AtomicWidgets\Styles\Atomic_Widget_Styles;
6 7 use Elementor\Modules\Mcp\Abilities\Appliers\V3\V3_Style_Mapper_Factory;
7 8 use Elementor\Modules\Mcp\Abilities\Utils\Bulk_Operations_Result;
8 9 use Elementor\Modules\Mcp\Abilities\Utils\Style_Variants_Merger;
9 10 use Elementor\Modules\Mcp\Abilities\Utils\Widget_Context_Helper;
11 +use Elementor\Modules\Variables\Utils\Variable_Type_Keys;
10 12 use Elementor\Plugin;
11 13
12 14 if ( ! defined( 'ABSPATH' ) ) {
13 15 exit;
@@ -19,8 +21,10 @@
19 21 const DESKTOP_BREAKPOINT = 'desktop';
20 22 const LOCAL_STYLE_LABEL = 'local';
21 23 const LOCAL_STYLE_TYPE = 'class';
22 24
25 + const UNSUPPORTED_CSS_CODE = 'unsupported_css';
26 +
23 27 private Css_Converter $css_converter;
24 28 private array $active_breakpoints;
25 29
26 30 public function __construct( Css_Converter $css_converter, array $active_breakpoints = [] ) {
@@ -32,21 +36,25 @@
32 36 * @param array<string, array&> $config_id_index Index of subtree refs.
33 37 * @param array<string, string> $styles Per-config-id CSS strings.
34 38 * @param string $style_apply_mode `patch` or `replace`.
35 39 * @param array<string, array> $widget_configs Optional widget_type => config map (used for V3 mapping).
36 - * @return array{error: \WP_Error|null, warnings: string[]}
40 + * @return array{error: \WP_Error|null, warnings: string[], variable_connections: array<string, array<string, string>>}
37 41 */
38 42 public function apply( array $config_id_index, array $styles, string $style_apply_mode = 'patch', array $widget_configs = [] ): array {
39 43 if ( empty( $styles ) ) {
40 44 return [
41 - 'error' => null,
42 - 'warnings' => [],
45 + 'error' => null,
46 + 'warnings' => [],
47 + 'warning_codes' => [],
48 + 'variable_connections' => [],
43 49 ];
44 50 }
45 51
46 - $active_breakpoints = $this->get_active_breakpoints();
47 - $errors = [];
48 - $warnings = [];
52 + $active_breakpoints = $this->get_active_breakpoints();
53 + $errors = [];
54 + $warnings = [];
55 + $warning_codes = [];
56 + $variable_connections = [];
49 57
50 58 foreach ( $styles as $config_id => $css_string ) {
51 59 if ( ! is_string( $css_string ) ) {
52 60 $errors[] = sprintf( '[%s] style must be a CSS string, got %s.', $config_id, gettype( $css_string ) );
@@ -59,12 +67,13 @@
59 67
60 68 $node = &$config_id_index[ $config_id ];
61 69
62 70 if ( V3_Node_Bridge::is_v3_node( $node ) ) {
63 - $v3_warnings = $this->apply_v3_style( $node, $css_string, $style_apply_mode, $widget_configs );
64 - foreach ( $v3_warnings as $warning ) {
71 + $v3_result = $this->apply_v3_style( $node, $css_string, $style_apply_mode, $widget_configs );
72 + foreach ( $v3_result['warnings'] as $warning ) {
65 73 $warnings[] = sprintf( '[%s] %s', $config_id, $warning );
66 74 }
75 + $warning_codes = array_merge( $warning_codes, $v3_result['codes'] );
67 76 unset( $node );
68 77 continue;
69 78 }
70 79
@@ -97,9 +106,25 @@
97 106 unset( $node );
98 107 continue;
99 108 }
100 109
101 - $new_variants = Style_Variants_Merger::build_variants( $parsed['breakpoint_blocks'], $this->css_converter );
110 + $built_variants = Style_Variants_Merger::build_variants_with_fallback_css( $parsed['breakpoint_blocks'], $this->css_converter );
111 + $new_variants = $built_variants['variants'];
112 + $unsupported_css = $this->collect_unsupported_css( $new_variants, $built_variants['fallback_css'] );
113 +
114 + if ( '' !== $unsupported_css ) {
115 + $warnings[] = sprintf(
116 + '[%s] %s',
117 + $config_id,
118 + sprintf(
119 + /* translators: %s: CSS declarations that were not saved */
120 + __( 'These CSS properties or values are not supported and were not saved: %s', 'elementor' ),
121 + self::truncate_css_snippet( $unsupported_css )
122 + )
123 + );
124 + $warning_codes[] = self::UNSUPPORTED_CSS_CODE;
125 + }
126 +
102 127 $affected_bps = array_column( $parsed['breakpoint_blocks'], 'breakpoint' );
103 128 $removal_bps = $parsed['removal_breakpoints'];
104 129 $existing_style_id = $this->find_existing_local_style_id( $node );
105 130 $existing_variants = $node['styles'][ $existing_style_id ]['variants'] ?? [];
@@ -113,19 +138,23 @@
113 138 $style_apply_mode,
114 139 $affected_bps
115 140 );
116 141
142 + $variable_connections[ $config_id ] = $this->collect_variable_connections( $new_variants );
143 +
117 144 $this->write_variants_to_node( $node, $merged_variants, $existing_style_id );
118 145 unset( $node );
119 146 }
120 147
121 148 return [
122 - 'error' => $errors ? new \WP_Error(
149 + 'error' => $errors ? new \WP_Error(
123 150 'elementor_invalid_styles',
124 151 implode( ' ', $errors ),
125 152 [ 'status' => \WP_Http::BAD_REQUEST ]
126 153 ) : null,
127 - 'warnings' => $warnings,
154 + 'warnings' => $warnings,
155 + 'warning_codes' => array_values( array_unique( $warning_codes ) ),
156 + 'variable_connections' => $variable_connections,
128 157 ];
129 158 }
130 159
131 160 /**
@@ -132,12 +161,13 @@
132 161 * @param array $node
133 162 * @param string $css_string
134 163 * @param string $style_apply_mode
135 164 * @param array<string, array> $widget_configs
136 - * @return string[] Warnings (without config-id prefix).
165 + * @return array{warnings: string[], codes: string[]} Warnings (without config-id prefix) and stable codes.
137 166 */
138 167 private function apply_v3_style( array &$node, string $css_string, string $style_apply_mode = 'patch', array $widget_configs = [] ): array {
139 168 $warnings = [];
169 + $codes = [];
140 170 $widget_type = $node['widgetType'] ?? '';
141 171 $widget_config = [];
142 172
143 173 if ( is_string( $widget_type ) && '' !== $widget_type ) {
@@ -153,9 +183,12 @@
153 183 V3_Node_Bridge::clear_style_settings( $node, (string) $widget_type, $widget_config );
154 184 }
155 185
156 186 if ( $is_empty_css ) {
157 - return $warnings;
187 + return [
188 + 'warnings' => $warnings,
189 + 'codes' => $codes,
190 + ];
158 191 }
159 192
160 193 $mapper = V3_Style_Mapper_Factory::create( $this->css_converter, $this->get_active_breakpoints() );
161 194 $result = $mapper->apply( $css_string, (string) $widget_type, $widget_config );
@@ -171,26 +204,34 @@
171 204 $unmapped = $result['unmapped_css'] ?? '';
172 205 $pro_warning = V3_Node_Bridge::apply_custom_css( $node, $unmapped, (string) $widget_type );
173 206 if ( null !== $pro_warning ) {
174 207 $warnings[] = $pro_warning;
208 + $codes[] = 'v3_style_needs_pro';
175 209 }
176 210
177 211 if ( '' !== trim( $unmapped ) ) {
178 212 $snippet = self::truncate_css_snippet( $unmapped );
179 - $warnings[] = null !== $pro_warning
180 - ? sprintf(
213 + if ( null !== $pro_warning ) {
214 + $warnings[] = sprintf(
181 215 /* translators: %s: CSS snippet that could not be mapped */
182 216 __( 'Some CSS could not be mapped to V3 settings and was dropped: %s', 'elementor' ),
183 217 $snippet
184 - )
185 - : sprintf(
218 + );
219 + $codes[] = 'css_dropped';
220 + } else {
221 + $warnings[] = sprintf(
186 222 /* translators: %s: CSS snippet that could not be mapped */
187 223 __( 'Some CSS could not be mapped to V3 settings and was written to custom_css: %s', 'elementor' ),
188 224 $snippet
189 225 );
226 + $codes[] = 'css_fallback_custom_css';
227 + }
190 228 }
191 229
192 - return $warnings;
230 + return [
231 + 'warnings' => $warnings,
232 + 'codes' => $codes,
233 + ];
193 234 }
194 235
195 236 private static function truncate_css_snippet( string $css, int $max_length = 200 ): string {
196 237 $css = trim( preg_replace( '/\s+/', ' ', $css ) ?? $css );
@@ -200,8 +241,32 @@
200 241
201 242 return substr( $css, 0, $max_length - 3 ) . '...';
202 243 }
203 244
245 + /**
246 + * @param array[] $variants
247 + * @param array<int, string> $fallback_css Per variant index, CSS the converter could not convert natively.
248 + */
249 + private function collect_unsupported_css( array $variants, array $fallback_css ): string {
250 + if ( empty( $fallback_css ) ) {
251 + return '';
252 + }
253 +
254 + $kept_variants = Atomic_Widget_Styles::get_license_based_filtered_styles(
255 + [ self::LOCAL_STYLE_LABEL => [ 'variants' => array_intersect_key( $variants, $fallback_css ) ] ]
256 + )[ self::LOCAL_STYLE_LABEL ]['variants'];
257 +
258 + $dropped_css = [];
259 +
260 + foreach ( $fallback_css as $index => $css ) {
261 + if ( empty( $kept_variants[ $index ]['custom_css'] ) ) {
262 + $dropped_css[] = $css;
263 + }
264 + }
265 +
266 + return implode( ' ', $dropped_css );
267 + }
268 +
204 269 private function get_active_breakpoints(): array {
205 270 if ( ! empty( $this->active_breakpoints ) ) {
206 271 return $this->active_breakpoints;
207 272 }
@@ -256,6 +321,47 @@
256 321 }
257 322
258 323 private function generate_local_style_id(): string {
259 324 return self::LOCAL_STYLE_ID_PREFIX . strtolower( \Elementor\Utils::generate_random_string() ) . '-' . dechex( wp_rand( 0x1000, 0xffff ) );
325 + }
326 +
327 + /**
328 + * @param array[] $variants
329 + * @return array<int, array{variable_id: string, var_type: string, control_path: string}>
330 + */
331 + private function collect_variable_connections( array $variants ): array {
332 + $connections = [];
333 +
334 + foreach ( $variants as $variant ) {
335 + foreach ( $variant['props'] ?? [] as $prop_key => $prop_value ) {
336 + if ( ! is_array( $prop_value ) ) {
337 + continue;
338 + }
339 +
340 + $type = $prop_value['$$type'] ?? '';
341 +
342 + if ( ! Variable_Type_Keys::is_variable_type( $type ) ) {
343 + continue;
344 + }
345 +
346 + $connections[] = [
347 + 'variable_id' => (string) ( $prop_value['value'] ?? '' ),
348 + 'var_type' => $this->resolve_var_type_label( $type ),
349 + 'control_path' => (string) $prop_key,
350 + ];
351 + }
352 + }
353 +
354 + return $connections;
355 + }
356 +
357 + private function resolve_var_type_label( string $type ): string {
358 + $labels = [
359 + 'global-color-variable' => 'color',
360 + 'global-font-variable' => 'font',
361 + 'global-size-variable' => 'size',
362 + 'global-custom-size-variable' => 'size',
363 + ];
364 +
365 + return $labels[ $type ] ?? $type;
260 366 }
261 367 }