PluginProbe
Elementor Website Builder – more than just a page builder / 3.32.2
Elementor Website Builder – more than just a page builder v3.32.2
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 4.0.7 All 451 releases
elementor / modules / atomic-widgets / parsers / style-parser.php

style-parser.php in Elementor Website Builder – more than just a page builder 3.32.2, at modules/atomic-widgets/parsers/style-parser.php

265 lines 6.6 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\Parsers;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit; // Exit if accessed directly.
7 }
8
9 use Elementor\Modules\AtomicWidgets\Opt_In;
10 use Elementor\Plugin;
11 use Elementor\Utils;
12 use Elementor\Core\Utils\Api\Parse_Result;
13
14 class Style_Parser {
15 const VALID_TYPES = [
16 'class',
17 ];
18
19 const VALID_STATES = [
20 'hover',
21 'active',
22 'focus',
23 null,
24 ];
25
26 private array $schema;
27
28 public function __construct( array $schema ) {
29 $this->schema = $schema;
30 }
31
32 public static function make( array $schema ): self {
33 return new static( $schema );
34 }
35
36 /**
37 * @param array $style
38 * the style object to validate
39 */
40 private function validate( array $style ): Parse_Result {
41 $validated_style = $style;
42 $result = Parse_Result::make();
43
44 if ( ! isset( $style['id'] ) || ! is_string( $style['id'] ) ) {
45 $result->errors()->add( 'id', 'missing_or_invalid' );
46 }
47
48 if ( ! isset( $style['type'] ) || ! in_array( $style['type'], self::VALID_TYPES, true ) ) {
49 $result->errors()->add( 'type', 'missing_or_invalid' );
50 }
51
52 if ( ! isset( $style['label'] ) || ! is_string( $style['label'] ) ) {
53 $result->errors()->add( 'label', 'missing_or_invalid' );
54 } elseif ( Plugin::$instance->experiments->is_feature_active( Opt_In::EXPERIMENT_NAME ) ) {
55 $label_validation = $this->validate_style_label( $style['label'] );
56
57 if ( ! $label_validation['is_valid'] ) {
58 $result->errors()->add( 'label', $label_validation['error_message'] );
59 }
60 }
61
62 if ( ! isset( $style['variants'] ) || ! is_array( $style['variants'] ) ) {
63 $result->errors()->add( 'variants', 'missing_or_invalid' );
64
65 unset( $validated_style['variants'] );
66
67 return $result->wrap( $validated_style );
68 }
69
70 $props_parser = Props_Parser::make( $this->schema );
71
72 foreach ( $style['variants'] as $variant_index => $variant ) {
73 if ( ! isset( $variant['meta'] ) ) {
74 $result->errors()->add( 'meta', 'missing' );
75
76 continue;
77 }
78
79 $meta_result = $this->validate_meta( $variant['meta'] );
80 $custom_css_result = $this->validate_custom_css( $variant );
81
82 $result->errors()->merge( $meta_result->errors(), 'meta' );
83 $result->errors()->merge( $custom_css_result->errors(), 'custom_css' );
84
85 if ( $meta_result->is_valid() ) {
86 $variant_result = $props_parser->validate( $variant['props'] );
87
88 $result->errors()->merge( $variant_result->errors(), "variants[$variant_index]" );
89
90 $validated_style['variants'][ $variant_index ]['props'] = $variant_result->unwrap();
91 } else {
92 unset( $validated_style['variants'][ $variant_index ] );
93 }
94 }
95
96 return $result->wrap( $validated_style );
97 }
98
99 private function validate_style_label( string $label ): array {
100 $label = strtolower( $label );
101
102 $reserved_class_names = [ 'container' ];
103
104 if ( strlen( $label ) > 50 ) {
105 return [
106 'is_valid' => false,
107 'error_message' => 'class_name_too_long',
108 ];
109 }
110
111 if ( strlen( $label ) < 2 ) {
112 return [
113 'is_valid' => false,
114 'error_message' => 'class_name_too_short',
115 ];
116 }
117
118 if ( in_array( $label, $reserved_class_names, true ) ) {
119 return [
120 'is_valid' => false,
121 'error_message' => 'reserved_class_name',
122 ];
123 }
124
125 $regexes = [
126 [
127 'pattern' => '/^(|[^0-9].*)$/',
128 'message' => 'class_name_starts_with_digit',
129 ],
130 [
131 'pattern' => '/^\S*$/',
132 'message' => 'class_name_contains_spaces',
133 ],
134 [
135 'pattern' => '/^(|[a-zA-Z0-9_-]+)$/',
136 'message' => 'class_name_invalid_chars',
137 ],
138 [
139 'pattern' => '/^(?!--).*/',
140 'message' => 'class_name_double_hyphen',
141 ],
142 [
143 'pattern' => '/^(?!-[0-9])/',
144 'message' => 'class_name_starts_with_hyphen_digit',
145 ],
146 ];
147
148 foreach ( $regexes as $rule ) {
149 if ( ! preg_match( $rule['pattern'], $label ) ) {
150 return [
151 'is_valid' => false,
152 'error_message' => $rule['message'],
153 ];
154 }
155 }
156 return [
157 'is_valid' => true,
158 'error_message' => null,
159 ];
160 }
161
162 private function validate_meta( $meta ): Parse_Result {
163 $result = Parse_Result::make();
164
165 if ( ! is_array( $meta ) ) {
166 $result->errors()->add( 'meta', 'invalid_type' );
167
168 return $result;
169 }
170
171 if ( ! array_key_exists( 'state', $meta ) || ! in_array( $meta['state'], self::VALID_STATES, true ) ) {
172 $result->errors()->add( 'state', 'missing_or_invalid_value' );
173
174 return $result;
175 }
176
177 // TODO: Validate breakpoint based on the existing breakpoints in the system [EDS-528]
178 if ( ! isset( $meta['breakpoint'] ) || ! is_string( $meta['breakpoint'] ) ) {
179 $result->errors()->add( 'breakpoint', 'missing_or_invalid_value' );
180
181 return $result;
182 }
183
184 return $result;
185 }
186
187 private function validate_custom_css( array $variant ): Parse_Result {
188 $result = Parse_Result::make();
189
190 if ( ! empty( $variant['custom_css']['raw'] ) && (
191 ! is_string( $variant['custom_css']['raw'] ) ||
192 null === Utils::decode_string( $variant['custom_css']['raw'], null )
193 )
194 ) {
195 $result->errors()->add( 'custom_css', 'invalid_type' );
196 }
197
198 return $result;
199 }
200
201 private function sanitize_meta( $meta ) {
202 if ( ! is_array( $meta ) ) {
203 return [];
204 }
205
206 if ( isset( $meta['breakpoint'] ) ) {
207 $meta['breakpoint'] = sanitize_key( $meta['breakpoint'] );
208 }
209
210 return $meta;
211 }
212
213 private function sanitize_custom_css( array $variant ) {
214 if ( empty( $variant['custom_css']['raw'] ) ) {
215 return null;
216 }
217
218 $custom_css = Utils::decode_string( $variant['custom_css']['raw'] );
219 $custom_css = sanitize_textarea_field( $custom_css );
220 $custom_css = [ 'raw' => Utils::encode_string( $custom_css ) ];
221
222 return empty( $custom_css['raw'] ) ? null : $custom_css;
223 }
224
225 /**
226 * @param array $style
227 * the style object to sanitize
228 */
229 private function sanitize( array $style ): Parse_Result {
230 $props_parser = Props_Parser::make( $this->schema );
231
232 if ( isset( $style['label'] ) ) {
233 $style['label'] = sanitize_text_field( $style['label'] );
234 }
235
236 if ( isset( $style['id'] ) ) {
237 $style['id'] = sanitize_key( $style['id'] );
238 }
239
240 if ( ! empty( $style['variants'] ) ) {
241 foreach ( $style['variants'] as $variant_index => $variant ) {
242 $style['variants'][ $variant_index ]['props'] = $props_parser->sanitize( $variant['props'] )->unwrap();
243 $style['variants'][ $variant_index ]['meta'] = $this->sanitize_meta( $variant['meta'] );
244 $style['variants'][ $variant_index ]['custom_css'] = $this->sanitize_custom_css( $variant );
245 }
246 }
247
248 return Parse_Result::make()->wrap( $style );
249 }
250
251 /**
252 * @param array $style
253 * the style object to parse
254 */
255 public function parse( array $style ): Parse_Result {
256 $validate_result = $this->validate( $style );
257
258 $sanitize_result = $this->sanitize( $validate_result->unwrap() );
259
260 $sanitize_result->errors()->merge( $validate_result->errors() );
261
262 return $sanitize_result;
263 }
264 }
265