PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.0-dev2
Elementor Website Builder – more than just a page builder v3.35.0-dev2
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.35.0-dev2, at modules/atomic-widgets/parsers/style-parser.php

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