PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
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 4.1.0-beta1 4.1.0-dev1 All 452 releases
← All changes | modules/atomic-widgets/css-converter/css-converter.php +116 -0 4.2.24.3.0-beta3 View file →
@@ -10,8 +10,11 @@
10 10 exit; // Exit if accessed directly.
11 11 }
12 12
13 13 class Css_Converter {
14 +
15 + use Css_Block_Scanner_Trait;
16 +
14 17 const BLOCKED_PROPERTIES = [ 'behavior', '-moz-binding' ];
15 18 const BLOCKED_VALUE_NEEDLES = [ 'expression(', 'javascript:' ];
16 19
17 20 private Converter_Registry $registry;
@@ -34,8 +37,116 @@
34 37 $this->variable_transformer = $variable_transformer;
35 38 }
36 39
37 40 /**
41 + * @return array{blocks: array<int, array{selector: string|null, css: string}>}|array{blocks: array, error: string}
42 + */
43 + public function parse_nested( string $css ): array {
44 + $result = $this->scan_ampersand_blocks( $css );
45 +
46 + if ( isset( $result['error'] ) ) {
47 + return [
48 + 'blocks' => [],
49 + 'error' => $result['error'],
50 + ];
51 + }
52 +
53 + return [
54 + 'blocks' => array_merge(
55 + [
56 + [
57 + 'selector' => null,
58 + 'css' => $result['base_css'],
59 + ],
60 + ],
61 + $result['nested_blocks']
62 + ),
63 + ];
64 + }
65 +
66 + private function scan_ampersand_blocks( string $css ): array {
67 + $nested_blocks = [];
68 + $base_css = '';
69 + $len = strlen( $css );
70 + $i = 0;
71 + $segment_start = 0;
72 + $in_string = false;
73 + $string_char = '';
74 +
75 + while ( $i < $len ) {
76 + $char = $css[ $i ];
77 +
78 + if ( $in_string ) {
79 + if ( $string_char === $char && ! $this->is_escaped( $css, $i ) ) {
80 + $in_string = false;
81 + }
82 + ++$i;
83 + continue;
84 + }
85 +
86 + if ( '"' === $char || "'" === $char ) {
87 + $in_string = true;
88 + $string_char = $char;
89 + ++$i;
90 + continue;
91 + }
92 +
93 + if ( '{' === $char ) {
94 + return [ 'error' => 'Unsupported nested selector. Only &:hover, &:focus, and &:active pseudo-class blocks are allowed.' ];
95 + }
96 +
97 + if ( '}' === $char ) {
98 + return [ 'error' => 'Unclosed brace detected in CSS input.' ];
99 + }
100 +
101 + if ( '&' !== $char ) {
102 + ++$i;
103 + continue;
104 + }
105 +
106 + $j = $i + 1;
107 + while ( $j < $len && '{' !== $css[ $j ] && ';' !== $css[ $j ] && '}' !== $css[ $j ] ) {
108 + ++$j;
109 + }
110 +
111 + if ( $j >= $len || ';' === $css[ $j ] || '}' === $css[ $j ] ) {
112 + ++$i;
113 + continue;
114 + }
115 +
116 + $selector = rtrim( substr( $css, $i + 1, $j - $i - 1 ) );
117 +
118 + if ( '' === $selector ) {
119 + return [ 'error' => 'Bare & block without a selector is not valid CSS.' ];
120 + }
121 +
122 + $block_end = $this->find_block_end( $css, $j + 1, $len );
123 +
124 + if ( null === $block_end ) {
125 + return [ 'error' => 'Unclosed brace detected in CSS input.' ];
126 + }
127 +
128 + $block_content = substr( $css, $j + 1, $block_end - $j - 2 );
129 + $base_css .= substr( $css, $segment_start, $i - $segment_start );
130 + $segment_start = $block_end;
131 +
132 + $nested_blocks[] = [
133 + 'selector' => $selector,
134 + 'css' => $block_content,
135 + ];
136 +
137 + $i = $block_end;
138 + }
139 +
140 + $base_css .= substr( $css, $segment_start );
141 +
142 + return [
143 + 'base_css' => $base_css,
144 + 'nested_blocks' => $nested_blocks,
145 + ];
146 + }
147 +
148 + /**
38 149 * @return array{props: array, customCss: string, rejected: string[]}
39 150 */
40 151 public function convert( string $css ): array {
41 152 $rules = $this->dedupe( $this->expand_shorthands( $this->parse( $css ) ) );
@@ -58,8 +169,13 @@
58 169 $ejected = $this->variable_transformer->eject_unresolved_var_props( $props, $schema, $rules );
59 170 $props = $ejected['props'];
60 171 $leftover = array_merge( $leftover, $ejected['custom_css'] );
61 172 $rejected = array_merge( $rejected, $ejected['rejected'] );
173 +
174 + // Post-processing: handle edge cases that the general transform pipeline cannot cover.
175 + $gradient_color_stops_leftovers = $this->variable_transformer->normalize_gradient_color_stops( $props, $rules );
176 + $leftover = array_merge( $leftover, $gradient_color_stops_leftovers );
177 +
62 178 $props = $this->validate_props( $props, $schema );
63 179 }
64 180
65 181 $props = $this->cleanup_props( $props );