PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0
Elementor Website Builder – more than just a page builder v4.3.0
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 4.1.0-dev2 All 455 releases
elementor / modules / mcp / abilities / appliers / v3 / v3-value-resolvers.php

v3-value-resolvers.php in Elementor Website Builder – more than just a page builder 4.3.0, at modules/mcp/abilities/appliers/v3/v3-value-resolvers.php

356 lines 9.1 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\V3;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 /**
10 * Pure functions that turn CSS values into legacy V3 control value shapes.
11 */
12 class V3_Value_Resolvers {
13
14 const DEFAULT_UNIT = 'px';
15
16 /**
17 * @return array{unit: string, size: float}|null
18 */
19 public static function resolve_dimension( string $css_value ): ?array {
20 $parsed = self::parse_length( trim( $css_value ) );
21
22 if ( null === $parsed ) {
23 return null;
24 }
25
26 return [
27 'unit' => $parsed['unit'],
28 'size' => $parsed['size'],
29 ];
30 }
31
32 /**
33 * Parses padding/margin/border-radius shorthand into Elementor dimensions shape.
34 *
35 * @return array{top: string, right: string, bottom: string, left: string, unit: string, isLinked: bool}|null
36 */
37 public static function resolve_sides_shorthand( string $css_value ): ?array {
38 $tokens = self::tokenize_css_values( trim( $css_value ) );
39
40 if ( empty( $tokens ) || count( $tokens ) > 4 ) {
41 return null;
42 }
43
44 $parsed = [];
45 foreach ( $tokens as $token ) {
46 $length = self::parse_length( $token );
47 if ( null === $length ) {
48 return null;
49 }
50 $parsed[] = $length;
51 }
52
53 $unit = $parsed[0]['unit'];
54 foreach ( $parsed as $item ) {
55 if ( $item['unit'] !== $unit ) {
56 return null;
57 }
58 }
59
60 $sizes = array_column( $parsed, 'size' );
61 $count = count( $sizes );
62
63 if ( 1 === $count ) {
64 $top = $sizes[0];
65 $right = $sizes[0];
66 $bottom = $sizes[0];
67 $left = $sizes[0];
68 } elseif ( 2 === $count ) {
69 $top = $sizes[0];
70 $bottom = $sizes[0];
71 $right = $sizes[1];
72 $left = $sizes[1];
73 } elseif ( 3 === $count ) {
74 $top = $sizes[0];
75 $right = $sizes[1];
76 $left = $sizes[1];
77 $bottom = $sizes[2];
78 } else {
79 [ $top, $right, $bottom, $left ] = $sizes;
80 }
81
82 $linked = $top === $right && $right === $bottom && $bottom === $left;
83
84 return [
85 'top' => (string) $top,
86 'right' => (string) $right,
87 'bottom' => (string) $bottom,
88 'left' => (string) $left,
89 'unit' => $unit,
90 'isLinked' => $linked,
91 ];
92 }
93
94 public static function resolve_color( string $css_value ): string {
95 return trim( $css_value );
96 }
97
98 /**
99 * Parses a single box-shadow declaration into Elementor's box_shadow control shape.
100 *
101 * @return array{box_shadow_type: string, box_shadow: array}|null
102 */
103 public static function resolve_box_shadow( string $css_value ): ?array {
104 $value = trim( $css_value );
105
106 if ( '' === $value || 'none' === strtolower( $value ) ) {
107 return [
108 'box_shadow_type' => '',
109 'box_shadow' => [
110 'horizontal' => 0,
111 'vertical' => 0,
112 'blur' => 0,
113 'spread' => 0,
114 'color' => 'rgba(0,0,0,0.5)',
115 ],
116 ];
117 }
118
119 $position = 'outline';
120 if ( preg_match( '/\binset\b/i', $value ) ) {
121 $position = 'inset';
122 $value = trim( preg_replace( '/\binset\b/i', '', $value ) );
123 }
124
125 $color = null;
126 if ( preg_match( '/^(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-fA-F]{3,8}|[a-zA-Z]+)\s+(.+)$/', $value, $matches ) ) {
127 $color = $matches[1];
128 $value = trim( $matches[2] );
129 } elseif ( preg_match( '/^(.+?)\s+(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-fA-F]{3,8}|[a-zA-Z]+)$/', $value, $matches ) ) {
130 $value = trim( $matches[1] );
131 $color = $matches[2];
132 }
133
134 $lengths = self::tokenize_css_values( $value );
135 if ( count( $lengths ) < 2 || count( $lengths ) > 4 ) {
136 return null;
137 }
138
139 $parsed_lengths = [];
140 foreach ( $lengths as $token ) {
141 $length = self::parse_length( $token );
142 if ( null === $length ) {
143 return null;
144 }
145 $parsed_lengths[] = $length['size'];
146 }
147
148 return [
149 'box_shadow_type' => 'yes',
150 'box_shadow' => [
151 'horizontal' => $parsed_lengths[0],
152 'vertical' => $parsed_lengths[1],
153 'blur' => $parsed_lengths[2] ?? 0,
154 'spread' => $parsed_lengths[3] ?? 0,
155 'color' => $color ?? 'rgba(0,0,0,0.5)',
156 'position' => $position,
157 ],
158 ];
159 }
160
161 /**
162 * Spreads typography-related CSS properties into legacy typography_* keys and
163 * forces the group toggle to `custom`.
164 *
165 * @param array<string, string> $declarations property => css value
166 * @param string $prefix Control group prefix, e.g. `typography` or `menu_typography`.
167 * @return array<string, mixed>
168 */
169 public static function resolve_typography_group( array $declarations, string $prefix = 'typography' ): array {
170 $patch = [];
171
172 $map = [
173 'font-family' => $prefix . '_font_family',
174 'font-weight' => $prefix . '_font_weight',
175 'text-transform' => $prefix . '_text_transform',
176 'font-style' => $prefix . '_font_style',
177 'text-decoration' => $prefix . '_text_decoration',
178 'line-height' => $prefix . '_line_height',
179 'letter-spacing' => $prefix . '_letter_spacing',
180 'word-spacing' => $prefix . '_word_spacing',
181 'font-size' => $prefix . '_font_size',
182 ];
183
184 foreach ( $declarations as $property => $value ) {
185 $key = $map[ $property ] ?? null;
186 if ( null === $key ) {
187 continue;
188 }
189
190 if ( in_array( $property, [ 'font-size', 'line-height', 'letter-spacing', 'word-spacing' ], true ) ) {
191 $dimension = self::resolve_dimension( $value );
192 if ( null !== $dimension ) {
193 $patch[ $key ] = $dimension;
194 }
195 continue;
196 }
197
198 if ( 'font-family' === $property ) {
199 $value = self::normalize_font_family( $value );
200 if ( '' === $value ) {
201 continue;
202 }
203 }
204
205 $patch[ $key ] = trim( $value );
206 }
207
208 if ( empty( $patch ) ) {
209 return [];
210 }
211
212 $patch[ $prefix . '_typography' ] = 'custom';
213
214 return $patch;
215 }
216
217 /**
218 * Parses `border: WIDTH STYLE COLOR` into Elementor border group keys.
219 *
220 * @param string $css_value
221 * @param string $prefix e.g. `border` or `image_border` or `dropdown_border`.
222 * @return array<string, mixed>|null
223 */
224 public static function resolve_border_shorthand( string $css_value, string $prefix = 'border' ): ?array {
225 $value = trim( $css_value );
226
227 if ( '' === $value || 'none' === strtolower( $value ) ) {
228 return [
229 $prefix . '_border' => '',
230 ];
231 }
232
233 $style = null;
234 $styles = [ 'solid', 'dashed', 'dotted', 'double', 'groove', 'ridge', 'inset', 'outset', 'none', 'hidden' ];
235 foreach ( $styles as $candidate ) {
236 if ( preg_match( '/\b' . preg_quote( $candidate, '/' ) . '\b/i', $value ) ) {
237 $style = strtolower( $candidate );
238 $value = trim( preg_replace( '/\b' . preg_quote( $candidate, '/' ) . '\b/i', '', $value ) );
239 break;
240 }
241 }
242
243 $color = null;
244 if ( preg_match( '/(rgba?\([^)]+\)|hsla?\([^)]+\)|#[0-9a-fA-F]{3,8}|[a-zA-Z]+)$/', $value, $matches ) ) {
245 $color = $matches[1];
246 $value = trim( substr( $value, 0, -strlen( $color ) ) );
247 }
248
249 $width = null;
250 $tokens = self::tokenize_css_values( $value );
251 if ( ! empty( $tokens ) ) {
252 $width = self::resolve_sides_shorthand( implode( ' ', $tokens ) );
253 }
254
255 $patch = [
256 $prefix . '_border' => $style ?? 'solid',
257 ];
258
259 if ( null !== $width ) {
260 $patch[ $prefix . '_width' ] = $width;
261 }
262
263 if ( null !== $color ) {
264 $patch[ $prefix . '_color' ] = $color;
265 }
266
267 return $patch;
268 }
269
270 /**
271 * Dispatches a named resolver against a CSS value.
272 *
273 * @param string $resolver_name One of: color, dimension, sides, box_shadow, border, text, slider.
274 * @param string $css_value
275 * @param array<string, mixed> $args Extra args (prefix for border/typography).
276 * @return mixed|null
277 */
278 public static function resolve( string $resolver_name, string $css_value, array $args = [] ) {
279 switch ( $resolver_name ) {
280 case 'color':
281 return self::resolve_color( $css_value );
282 case 'dimension':
283 return self::resolve_dimension( $css_value );
284 case 'sides':
285 return self::resolve_sides_shorthand( $css_value );
286 case 'box_shadow':
287 return self::resolve_box_shadow( $css_value );
288 case 'border':
289 return self::resolve_border_shorthand( $css_value, $args['prefix'] ?? 'border' );
290 case 'slider':
291 $dimension = self::resolve_dimension( $css_value );
292 return null === $dimension ? null : $dimension;
293 case 'text':
294 return trim( $css_value );
295 default:
296 return null;
297 }
298 }
299
300 /**
301 * @return array{size: float, unit: string}|null
302 */
303 private static function parse_length( string $token ): ?array {
304 $token = trim( $token );
305
306 if ( '' === $token ) {
307 return null;
308 }
309
310 if ( '0' === $token ) {
311 return [
312 'size' => 0.0,
313 'unit' => self::DEFAULT_UNIT,
314 ];
315 }
316
317 if ( ! preg_match( '/^(-?\d*\.?\d+)(px|em|rem|%|vh|vw|vmin|vmax)?$/i', $token, $matches ) ) {
318 return null;
319 }
320
321 return [
322 'size' => (float) $matches[1],
323 'unit' => strtolower( $matches[2] ?? self::DEFAULT_UNIT ),
324 ];
325 }
326
327 /**
328 * V3 typography controls accept a single named font; CSS stacks are reduced to the first family.
329 */
330 private static function normalize_font_family( string $value ): string {
331 $first = trim( explode( ',', $value, 2 )[0] );
332
333 if (
334 ( str_starts_with( $first, '"' ) && str_ends_with( $first, '"' ) )
335 || ( str_starts_with( $first, "'" ) && str_ends_with( $first, "'" ) )
336 ) {
337 $first = substr( $first, 1, -1 );
338 }
339
340 return trim( $first );
341 }
342
343 /**
344 * @return string[]
345 */
346 private static function tokenize_css_values( string $value ): array {
347 if ( '' === $value ) {
348 return [];
349 }
350
351 preg_match_all( '/(?:rgba?\([^)]+\)|hsla?\([^)]+\)|[^\s]+)/', $value, $matches );
352
353 return $matches[0] ?? [];
354 }
355 }
356