PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / trunk
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More vtrunk
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
← All changes | classes/helpers/FrmCssScopeHelper.php +132 -27 6.26 → trunk View file →
@@ -32,8 +32,9 @@
32 32
33 33 if ( false === $brace_pos ) {
34 34 $buffer .= $char;
35 35 ++$i;
36 +
36 37 continue;
37 38 }
38 39
39 40 $rule = substr( $css, $i, $brace_pos - $i );
@@ -40,9 +41,9 @@
40 41 $closing_brace = $this->find_matching_brace( $css, $brace_pos );
41 42 $inner_content = substr( $css, $brace_pos + 1, $closing_brace - $brace_pos - 1 );
42 43
43 44 // Don't nest keyframes content
44 - if ( strpos( $rule, '@keyframes' ) !== false ) {
45 + if ( str_contains( $rule, '@keyframes' ) ) {
45 46 $output[] = "\n" . $rule . ' {' . $inner_content . '}' . "\n";
46 47 } else {
47 48 $output[] = "\n" . $rule . ' {';
48 49 $output[] = $this->nest( $inner_content, $class_name );
@@ -50,8 +51,9 @@
50 51 }
51 52
52 53 $i = $closing_brace + 1;
53 54 $buffer = '';
55 +
54 56 continue;
55 57 }//end if
56 58
57 59 if ( '{' === $char ) {
@@ -62,19 +64,11 @@
62 64 // Preserve indentation and formatting of declarations
63 65 $declarations = $this->preserve_declaration_formatting( $declarations );
64 66
65 67 if ( '' !== $selector && '' !== trim( $declarations ) ) {
66 - // Handle multiple selectors
67 - $selectors = array_map( 'trim', explode( ',', $selector ) );
68 - $prefixed_selectors = array();
68 + $prefixed_selectors = $this->prefix_selectors( $selector, $class_name );
69 69
70 - foreach ( $selectors as $single_selector ) {
71 - if ( '' !== $single_selector ) {
72 - $prefixed_selectors[] = '.' . $class_name . ' ' . $single_selector;
73 - }
74 - }
75 -
76 - if ( ! empty( $prefixed_selectors ) ) {
70 + if ( $prefixed_selectors ) {
77 71 $output[] = "\n" . implode( ',' . "\n", $prefixed_selectors ) . ' {' . $declarations . '}' . "\n";
78 72 }
79 73 }
80 74
@@ -79,8 +73,9 @@
79 73 }
80 74
81 75 $i = $closing_brace + 1;
82 76 $buffer = '';
77 +
83 78 continue;
84 79 }//end if
85 80
86 81 $buffer .= $char;
@@ -133,8 +128,9 @@
133 128 $output[] = '}' . "\n";
134 129
135 130 $i = $closing_brace + 1;
136 131 $buffer = '';
132 +
137 133 continue;
138 134 }//end if
139 135
140 136 if ( '{' === $char ) {
@@ -145,24 +141,11 @@
145 141 // Preserve indentation and formatting of declarations
146 142 $declarations = $this->preserve_declaration_formatting( $declarations );
147 143
148 144 if ( '' !== $selector && '' !== trim( $declarations ) ) {
149 - // Handle multiple selectors
150 - $selectors = array_filter(
151 - array_map( 'trim', explode( ',', $selector ) ),
152 - function ( $s ) {
153 - return '' !== $s;
154 - }
155 - );
156 - $unprefixed_selectors = array();
145 + $unprefixed_selectors = $this->unprefix_selectors( $selector, $class_name, $prefix, $prefix_length );
157 146
158 - foreach ( $selectors as $single_selector ) {
159 - $unprefixed_selectors[] = 0 === strpos( $single_selector, $prefix )
160 - ? trim( substr( $single_selector, $prefix_length ) )
161 - : $single_selector;
162 - }
163 -
164 - if ( ! empty( $unprefixed_selectors ) ) {
147 + if ( $unprefixed_selectors ) {
165 148 $output[] = "\n" . implode( ',' . "\n", $unprefixed_selectors ) . ' {' . $declarations . '}' . "\n";
166 149 }
167 150 }
168 151
@@ -167,8 +150,9 @@
167 150 }
168 151
169 152 $i = $closing_brace + 1;
170 153 $buffer = '';
154 +
171 155 continue;
172 156 }//end if
173 157
174 158 $buffer .= $char;
@@ -173,8 +157,9 @@
173 157
174 158 $buffer .= $char;
175 159 ++$i;
176 160 }//end while
161 +
177 162 return implode( '', $output );
178 163 }
179 164
180 165 /**
@@ -192,9 +177,9 @@
192 177 return '';
193 178 }
194 179
195 180 // Check if declarations are already on multiple lines
196 - if ( strpos( $declarations, "\n" ) !== false ) {
181 + if ( str_contains( $declarations, "\n" ) ) {
197 182 // Already formatted - preserve it
198 183 $lines = explode( "\n", $declarations );
199 184 $formatted_lines = array();
200 185
@@ -213,8 +198,127 @@
213 198 return ' ' . $declarations . ' ';
214 199 }
215 200
216 201 /**
202 + * Build the list of prefixed selectors for a given selector string.
203 + * Generates both descendant (.scope selector) and direct (selector.scope) forms.
204 + *
205 + * @param string $selector The comma-separated selector string.
206 + * @param string $class_name The scope class name.
207 + *
208 + * @return array The list of prefixed selectors.
209 + */
210 + private function prefix_selectors( $selector, $class_name ) {
211 + $selectors = array_map( 'trim', explode( ',', $selector ) );
212 + $prefixed_selectors = array();
213 +
214 + foreach ( $selectors as $single_selector ) {
215 + if ( '' === $single_selector ) {
216 + continue;
217 + }
218 +
219 + $prefixed_selectors[] = '.' . $class_name . ' ' . $single_selector;
220 + $direct = $this->add_direct_scope( $single_selector, $class_name );
221 +
222 + if ( null !== $direct ) {
223 + $prefixed_selectors[] = $direct;
224 + }
225 + }
226 +
227 + return $prefixed_selectors;
228 + }
229 +
230 + /**
231 + * Build the list of unprefixed selectors for a given selector string.
232 + * Handles both descendant (.scope selector) and direct (selector.scope) forms.
233 + *
234 + * @param string $selector The comma-separated selector string.
235 + * @param string $class_name The scope class name.
236 + * @param string $prefix The descendant prefix string (.class_name followed by space).
237 + * @param int $prefix_length The length of the descendant prefix.
238 + *
239 + * @return array The list of unprefixed selectors (deduplicated).
240 + */
241 + private function unprefix_selectors( $selector, $class_name, $prefix, $prefix_length ) {
242 + $selectors = array_filter(
243 + array_map( 'trim', explode( ',', $selector ) ),
244 + function ( $s ) {
245 + return '' !== $s;
246 + }
247 + );
248 + $unprefixed_selectors = array();
249 +
250 + foreach ( $selectors as $single_selector ) {
251 + if ( str_starts_with( $single_selector, $prefix ) ) {
252 + $unprefixed_selectors[] = trim( substr( $single_selector, $prefix_length ) );
253 + } else {
254 + $unprefixed_selectors[] = $this->remove_direct_scope( $single_selector, $class_name ) ?? $single_selector;
255 + }
256 + }
257 +
258 + return array_values( array_unique( $unprefixed_selectors ) );
259 + }
260 +
261 + /**
262 + * Add the scope class directly to the first segment of a selector.
263 + * For example, "h2" becomes "h2.scope" and ".foo .bar" becomes ".foo.scope .bar".
264 + *
265 + * @param string $selector The CSS selector.
266 + * @param string $class_name The scope class name.
267 + *
268 + * @return string|null The direct-scoped selector, or null if not applicable.
269 + */
270 + private function add_direct_scope( $selector, $class_name ) {
271 + $parts = preg_split( '/(\s+)/', $selector, 2, PREG_SPLIT_DELIM_CAPTURE );
272 + $first = $parts[0];
273 +
274 + // Don't add direct scope to selectors starting with combinators or universal selector.
275 + if ( preg_match( '/^[>+~*]/', $first ) ) {
276 + return null;
277 + }
278 +
279 + $rest = '';
280 +
281 + if ( count( $parts ) > 1 ) {
282 + $rest = $parts[1] . $parts[2];
283 + }
284 +
285 + // Capture all trailing pseudo-classes/pseudo-elements ( :before, :hover, etc. )
286 + // Capture the chained pseudos wraps (.form_field:nth-child(2):focus )
287 + $pseudo = '';
288 +
289 + if ( preg_match( '/^(.*?)((?::{1,2}[a-zA-Z-]+(?:\([^)]*\))?)+)$/', $first, $pseudo_parts ) ) {
290 + $first = $pseudo_parts[1];
291 + $pseudo = $pseudo_parts[2];
292 + }
293 +
294 + return $first . '.' . $class_name . $pseudo . $rest;
295 + }
296 +
297 + /**
298 + * Remove the scope class from the first segment of a direct-scoped selector.
299 + * For example, "h2.scope" becomes "h2" and ".foo.scope .bar" becomes ".foo .bar".
300 + *
301 + * @param string $selector The CSS selector.
302 + * @param string $class_name The scope class name.
303 + *
304 + * @return string|null The unscoped selector, or null if the scope was not found.
305 + */
306 + private function remove_direct_scope( $selector, $class_name ) {
307 + $scope_pattern = '/\.' . preg_quote( $class_name, '/' ) . '(?![a-zA-Z0-9_-])/';
308 + $parts = preg_split( '/(\s+)/', $selector, 2, PREG_SPLIT_DELIM_CAPTURE );
309 +
310 + if ( false === $parts || ! preg_match( $scope_pattern, $parts[0] ) ) {
311 + return null;
312 + }
313 +
314 + $parts[0] = preg_replace( $scope_pattern, '', $parts[0], 1 );
315 + $result = trim( implode( '', $parts ) );
316 +
317 + return '' !== $result ? $result : null;
318 + }
319 +
320 + /**
217 321 * Find the matching brace in the CSS.
218 322 *
219 323 * @param string $css
220 324 * @param int $open_pos
@@ -237,8 +341,9 @@
237 341 $string_char = $char;
238 342 } elseif ( $char === $string_char ) {
239 343 $in_string = false;
240 344 }
345 +
241 346 continue;
242 347 }
243 348
244 349 // Skip braces inside strings