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.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 / atomic-widgets / css-converter / value-parsers / background-image-value-parser.php

background-image-value-parser.php in Elementor Website Builder – more than just a page builder 4.3.0, at modules/atomic-widgets/css-converter/value-parsers/background-image-value-parser.php

277 lines 7.0 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\AtomicWidgets\CssConverter\ValueParsers;
4
5 use Elementor\Modules\AtomicWidgets\PropTypes\Background_Gradient_Overlay_Prop_Type;
6 use Elementor\Modules\AtomicWidgets\PropTypes\Background_Image_Overlay_Prop_Type;
7 use Elementor\Modules\AtomicWidgets\PropTypes\Color_Prop_Type;
8 use Elementor\Modules\AtomicWidgets\PropTypes\Color_Stop_Prop_Type;
9 use Elementor\Modules\AtomicWidgets\PropTypes\Gradient_Color_Stop_Prop_Type;
10 use Elementor\Modules\AtomicWidgets\PropTypes\Image_Prop_Type;
11 use Elementor\Modules\AtomicWidgets\PropTypes\Image_Src_Prop_Type;
12 use Elementor\Modules\AtomicWidgets\PropTypes\Position_Prop_Type;
13 use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\Number_Prop_Type;
14 use Elementor\Modules\AtomicWidgets\PropTypes\Primitives\String_Prop_Type;
15 use Elementor\Modules\AtomicWidgets\PropTypes\Url_Prop_Type;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Stateless parser: a raw CSS `background-image` value -> an ordered list of overlay PropValues (one
23 * per comma-separated layer), or null to decline the whole declaration (-> custom_css).
24 *
25 * Each layer token is classified:
26 * - `none` -> layer is silently skipped (not added to the list).
27 * - `url(...)` -> Background_Image_Overlay_Prop_Type PropValue.
28 * - `linear-gradient(...)` -> Background_Gradient_Overlay_Prop_Type PropValue (linear).
29 * - `radial-gradient(...)` -> Background_Gradient_Overlay_Prop_Type PropValue (radial).
30 * - anything else -> decline the entire value.
31 *
32 * Gradient parsing supports:
33 * - Linear: optional leading `Ndeg` angle, then comma-separated color stops.
34 * - Radial: optional leading `circle at <named-position>`, then color stops.
35 * - Color stops: `<color> <N>%` pairs; offset is optional.
36 * - Unsupported syntax (direction keywords, conic, complex shapes) declines.
37 */
38 class Background_Image_Value_Parser {
39 const DEFAULT_IMAGE_SIZE = 'large';
40
41 /**
42 * @return array[]|null Ordered overlay PropValues per layer, or null to decline.
43 */
44 public static function parse( string $value ): ?array {
45 $tokens = Css_Token_Splitter::split_by_comma( trim( $value ) );
46
47 if ( empty( $tokens ) ) {
48 return null;
49 }
50
51 $overlays = [];
52
53 foreach ( $tokens as $token ) {
54 $overlay = self::parse_layer( trim( $token ) );
55
56 if ( false === $overlay ) {
57 return null;
58 }
59
60 if ( null !== $overlay ) {
61 $overlays[] = $overlay;
62 }
63 }
64
65 return $overlays;
66 }
67
68 /**
69 * @return array|null|false PropValue, null for `none`, false to decline.
70 */
71 private static function parse_layer( string $token ) {
72 $lower = strtolower( $token );
73
74 if ( 'none' === $lower ) {
75 return null;
76 }
77
78 if ( 0 === strpos( $lower, 'url(' ) ) {
79 return self::parse_url( $token );
80 }
81
82 if ( preg_match( '/^([\w-]+-gradient)\s*\((.+)\)$/si', $token, $m ) ) {
83 return self::parse_gradient( strtolower( $m[1] ), $m[2] );
84 }
85
86 return false;
87 }
88
89 /**
90 * @return array|false
91 */
92 private static function parse_url( string $token ) {
93 if ( ! preg_match( '/^url\s*\(\s*([\'"]?)(.+?)\1\s*\)$/i', $token, $m ) ) {
94 return false;
95 }
96
97 $url = $m[2];
98
99 return Background_Image_Overlay_Prop_Type::generate( [
100 'image' => Image_Prop_Type::generate( [
101 'src' => Image_Src_Prop_Type::generate( [
102 'url' => Url_Prop_Type::generate( $url ),
103 ] ),
104 'size' => String_Prop_Type::generate( self::DEFAULT_IMAGE_SIZE ),
105 ] ),
106 ] );
107 }
108
109 /**
110 * @return array|false
111 */
112 private static function parse_gradient( string $func_name, string $args ) {
113 if ( 'linear-gradient' === $func_name ) {
114 return self::parse_linear_gradient( $args );
115 }
116
117 if ( 'radial-gradient' === $func_name ) {
118 return self::parse_radial_gradient( $args );
119 }
120
121 return false;
122 }
123
124 /**
125 * @return array|false
126 */
127 private static function parse_linear_gradient( string $args ) {
128 $tokens = Css_Token_Splitter::split_by_comma( trim( $args ) );
129
130 if ( empty( $tokens ) ) {
131 return false;
132 }
133
134 $angle = null;
135 $stop_start = 0;
136 $first = trim( $tokens[0] );
137
138 if ( preg_match( '/^(-?\d+(?:\.\d+)?)deg$/i', $first, $m ) ) {
139 $angle = (float) $m[1];
140 $stop_start = 1;
141 } elseif ( 0 === strpos( strtolower( $first ), 'to ' ) ) {
142 return false;
143 }
144
145 $stops = self::parse_color_stops( array_slice( $tokens, $stop_start ) );
146
147 if ( null === $stops ) {
148 return false;
149 }
150
151 $value = [
152 'type' => String_Prop_Type::generate( 'linear' ),
153 'stops' => Gradient_Color_Stop_Prop_Type::generate( $stops ),
154 ];
155
156 if ( null !== $angle ) {
157 $value['angle'] = Number_Prop_Type::generate( $angle );
158 }
159
160 return Background_Gradient_Overlay_Prop_Type::generate( $value );
161 }
162
163 /**
164 * @return array|false
165 */
166 private static function parse_radial_gradient( string $args ) {
167 $tokens = Css_Token_Splitter::split_by_comma( trim( $args ) );
168
169 if ( empty( $tokens ) ) {
170 return false;
171 }
172
173 $positions = null;
174 $stop_start = 0;
175 $first = trim( $tokens[0] );
176
177 if ( preg_match( '/^circle\s+at\s+(.+)$/i', $first, $m ) ) {
178 $pos = trim( $m[1] );
179
180 if ( ! Position_Prop_Type::is_valid_radial_position( $pos ) ) {
181 return false;
182 }
183
184 $positions = $pos;
185 $stop_start = 1;
186 }
187
188 $stops = self::parse_color_stops( array_slice( $tokens, $stop_start ) );
189
190 if ( null === $stops ) {
191 return false;
192 }
193
194 $value = [
195 'type' => String_Prop_Type::generate( 'radial' ),
196 'stops' => Gradient_Color_Stop_Prop_Type::generate( $stops ),
197 ];
198
199 if ( null !== $positions ) {
200 $value['positions'] = String_Prop_Type::generate( $positions );
201 }
202
203 return Background_Gradient_Overlay_Prop_Type::generate( $value );
204 }
205
206 /**
207
208 * @return array[]|null
209 */
210 private static function parse_color_stops( array $tokens ): ?array {
211 if ( empty( $tokens ) ) {
212 return null;
213 }
214
215 $parsed = [];
216
217 foreach ( $tokens as $token ) {
218 $parts = self::parse_color_stop_parts( trim( $token ) );
219
220 if ( null === $parts ) {
221 return null;
222 }
223
224 $parsed[] = $parts;
225 }
226
227 return self::build_color_stops_with_defaults( $parsed );
228 }
229
230 private static function parse_color_stop_parts( string $token ): ?array {
231 $parts = Css_Token_Splitter::split_by_whitespace( $token );
232
233 if ( empty( $parts ) || count( $parts ) > 2 ) {
234 return null;
235 }
236
237 $offset = null;
238
239 if ( isset( $parts[1] ) ) {
240 if ( ! preg_match( '/^(-?\d+(?:\.\d+)?)%$/', $parts[1], $m ) ) {
241 return null;
242 }
243
244 $offset = (float) $m[1];
245 }
246
247 return [
248 'color' => $parts[0],
249 'offset' => $offset,
250 ];
251 }
252
253 private static function build_color_stops_with_defaults( array $parsed ): array {
254 $count = count( $parsed );
255 $stops = [];
256
257 foreach ( $parsed as $index => $part ) {
258 $offset = $part['offset'] ?? self::default_offset_for_index( $index, $count );
259
260 $stops[] = Color_Stop_Prop_Type::generate( [
261 'color' => Color_Prop_Type::generate( $part['color'] ),
262 'offset' => Number_Prop_Type::generate( $offset ),
263 ] );
264 }
265
266 return $stops;
267 }
268
269 private static function default_offset_for_index( int $index, int $count ): float {
270 if ( $count <= 1 ) {
271 return 0.0;
272 }
273
274 return ( $index / ( $count - 1 ) ) * 100;
275 }
276 }
277