PluginProbe
Elementor Website Builder – more than just a page builder / 4.2.0
Elementor Website Builder – more than just a page builder v4.2.0
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 / interactions / validation.php

validation.php in Elementor Website Builder – more than just a page builder 4.2.0, at modules/interactions/validation.php

417 lines 10.9 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\Interactions;
4
5 use Elementor\Modules\Interactions\Validators\Breakpoints_Value as BreakpointsValueValidator;
6 use Elementor\Modules\Interactions\Validators\Custom_Effect_Value;
7 use Elementor\Modules\Interactions\Validators\String_Value as StringValueValidator;
8 use Elementor\Modules\Interactions\Validators\Trigger_Value as TriggerValueValidator;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 class Validation {
15 private $elements_to_interactions_counter = [];
16 private $max_number_of_interactions = 5;
17
18 private const VALID_EFFECTS = [ 'fade', 'slide', 'scale', 'custom' ];
19 private const VALID_TYPES = [ 'in', 'out' ];
20 private const VALID_DIRECTIONS = [ '', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right' ];
21 private const VALID_REPEAT_MODES = [ '', 'loop', 'times' ];
22
23 public function sanitize( $document ) {
24 return $this->sanitize_document_data( $document );
25 }
26
27 public function validate() {
28 foreach ( $this->elements_to_interactions_counter as $element_id => $number_of_interactions ) {
29 if ( $number_of_interactions > $this->max_number_of_interactions ) {
30 throw new \Exception(
31 sprintf(
32 // translators: %1$s: element ID, %2$d: maximum number of interactions allowed.
33 esc_html__( 'Element %1$s has more than %2$d interactions', 'elementor' ),
34 esc_html( $element_id ),
35 esc_html( $this->max_number_of_interactions )
36 )
37 );
38 }
39 }
40
41 return true;
42 }
43
44 private function sanitize_document_data( $data ) {
45 if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) {
46 $data['elements'] = $this->sanitize_elements_interactions( $data['elements'] );
47 }
48
49 return $data;
50 }
51
52 private function sanitize_elements_interactions( $elements ) {
53 if ( ! is_array( $elements ) ) {
54 return $elements;
55 }
56
57 foreach ( $elements as &$element ) {
58 if ( isset( $element['interactions'] ) ) {
59 $element['interactions'] = $this->sanitize_interactions( $element['interactions'], $element['id'] );
60 }
61
62 if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) {
63 $element['elements'] = $this->sanitize_elements_interactions( $element['elements'] );
64 }
65 }
66
67 return $elements;
68 }
69
70 private function decode_interactions( $interactions ) {
71 if ( is_array( $interactions ) ) {
72 if ( isset( $interactions['items']['$$type'] ) && 'array' === $interactions['items']['$$type'] ) {
73 return isset( $interactions['items']['value'] ) ? $interactions['items']['value'] : [];
74 }
75 return isset( $interactions['items'] ) ? $interactions['items'] : [];
76 }
77
78 if ( is_string( $interactions ) ) {
79 $decoded = json_decode( $interactions, true );
80 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
81 if ( isset( $decoded['items']['$$type'] ) && 'array' === $decoded['items']['$$type'] ) {
82 return isset( $decoded['items']['value'] ) ? $decoded['items']['value'] : [];
83 }
84 return isset( $decoded['items'] ) ? $decoded['items'] : [];
85 }
86 }
87
88 return [];
89 }
90
91 private function increment_interactions_counter_for( $element_id ) {
92 if ( ! array_key_exists( $element_id, $this->elements_to_interactions_counter ) ) {
93 $this->elements_to_interactions_counter[ $element_id ] = 0;
94 }
95
96 ++$this->elements_to_interactions_counter[ $element_id ];
97
98 return $this;
99 }
100
101 private function sanitize_interactions( $interactions, $element_id ) {
102 $sanitized = [
103 'items' => [],
104 'version' => 1,
105 ];
106
107 $list_of_interactions = $this->decode_interactions( $interactions );
108
109 foreach ( $list_of_interactions as $interaction ) {
110 if ( $this->is_valid_interaction_item( $interaction ) ) {
111 $sanitized['items'][] = $interaction;
112 $this->increment_interactions_counter_for( $element_id );
113 }
114 }
115
116 if ( empty( $sanitized['items'] ) ) {
117 return [];
118 }
119
120 return wp_json_encode( $sanitized );
121 }
122
123 private function is_valid_interaction_item( $item ) {
124 if ( ! is_array( $item ) ) {
125 return false;
126 }
127
128 // Validate PropType format: { $$type: 'interaction-item', value: { ... } }
129 if ( ! isset( $item['$$type'] ) || 'interaction-item' !== $item['$$type'] ) {
130 return false;
131 }
132
133 if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
134 return false;
135 }
136
137 $value = $item['value'];
138
139 // Validate required fields exist
140 if ( isset( $value['interaction_id'] ) && ! $this->is_valid_string_prop( $value, 'interaction_id' ) ) {
141 return false;
142 }
143
144 if ( ! $this->is_valid_trigger_prop( $value ) ) {
145 return false;
146 }
147
148 if ( ! $this->is_valid_animation_prop( $value ) ) {
149 return false;
150 }
151
152 if ( ! $this->is_valid_breakpoints_prop( $value ) ) {
153 return false;
154 }
155
156 return true;
157 }
158
159 private function is_valid_trigger_prop( $data ) {
160 if ( ! array_key_exists( 'trigger', $data ) ) {
161 return false;
162 }
163
164 return TriggerValueValidator::is_valid( $data['trigger'] );
165 }
166
167 private function is_valid_breakpoints_prop( $data ) {
168 if ( array_key_exists( 'breakpoints', $data ) ) {
169 return BreakpointsValueValidator::is_valid( $data['breakpoints'] );
170 }
171
172 return true;
173 }
174
175 private function is_valid_string_prop( $data, $key, $allowed_values = null ) {
176 if ( ! isset( $data[ $key ] ) ) {
177 return false;
178 }
179
180 return StringValueValidator::is_valid( $data[ $key ], $allowed_values );
181 }
182
183 private function is_valid_boolean_prop( $data, $key ) {
184 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
185 return false;
186 }
187
188 $prop = $data[ $key ];
189
190 if ( ! isset( $prop['$$type'] ) || 'boolean' !== $prop['$$type'] ) {
191 return false;
192 }
193
194 if ( ! isset( $prop['value'] ) || ! is_bool( $prop['value'] ) ) {
195 return false;
196 }
197
198 return true;
199 }
200
201 private function is_valid_number_prop( $data, $key ) {
202 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
203 return false;
204 }
205
206 $prop = $data[ $key ];
207
208 if ( ! isset( $prop['$$type'] ) || 'number' !== $prop['$$type'] ) {
209 return false;
210 }
211
212 if ( ! isset( $prop['value'] ) || ! is_numeric( $prop['value'] ) ) {
213 return false;
214 }
215
216 return true;
217 }
218
219 private function is_valid_number_prop_in_range( $data, $key, $min = null, $max = null ) {
220 if ( ! $this->is_valid_number_prop( $data, $key ) ) {
221 return false;
222 }
223
224 $value = (float) $data[ $key ]['value'];
225
226 if ( null !== $min && $value < $min ) {
227 return false;
228 }
229
230 if ( null !== $max && $value > $max ) {
231 return false;
232 }
233
234 return true;
235 }
236
237 private function is_valid_config_prop( $data ) {
238 if ( ! isset( $data['config'] ) || ! is_array( $data['config'] ) ) {
239 return false;
240 }
241
242 $config_value = $data['config']['value'];
243
244 if ( isset( $config_value['replay'] ) && ! $this->is_valid_boolean_prop( $config_value, 'replay' ) ) {
245 return false;
246 }
247
248 if ( isset( $config_value['easing'] ) && ! $this->is_valid_string_prop( $config_value, 'easing' ) ) {
249 return false;
250 }
251
252 if ( isset( $config_value['relativeTo'] ) && ! $this->is_valid_string_prop( $config_value, 'relativeTo' ) ) {
253 return false;
254 }
255
256 if ( isset( $config_value['repeat'] ) && ! $this->is_valid_string_prop( $config_value, 'repeat', self::VALID_REPEAT_MODES ) ) {
257 return false;
258 }
259
260 if ( isset( $config_value['times'] ) && ! $this->is_valid_number_prop_in_range( $config_value, 'times', 1 ) ) {
261 return false;
262 }
263
264 if ( isset( $config_value['start'] ) && ! $this->is_valid_size_prop_in_range( $config_value, 'start', 0, 100 ) ) {
265 return false;
266 }
267
268 if ( isset( $config_value['end'] ) && ! $this->is_valid_size_prop_in_range( $config_value, 'end', 0, 100 ) ) {
269 return false;
270 }
271
272 return true;
273 }
274
275 private function is_valid_animation_prop( $data ) {
276 if ( ! isset( $data['animation'] ) || ! is_array( $data['animation'] ) ) {
277 return false;
278 }
279
280 $animation = $data['animation'];
281
282 if ( ! isset( $animation['$$type'] ) || 'animation-preset-props' !== $animation['$$type'] ) {
283 return false;
284 }
285
286 if ( ! isset( $animation['value'] ) || ! is_array( $animation['value'] ) ) {
287 return false;
288 }
289
290 $animation_value = $animation['value'];
291
292 // Validate effect
293 if ( ! $this->is_valid_string_prop( $animation_value, 'effect', self::VALID_EFFECTS ) ) {
294 return false;
295 }
296
297 // Validate type
298 if ( ! $this->is_valid_string_prop( $animation_value, 'type', self::VALID_TYPES ) ) {
299 return false;
300 }
301
302 // Validate direction (can be empty string)
303 if ( ! $this->is_valid_string_prop( $animation_value, 'direction', self::VALID_DIRECTIONS ) ) {
304 return false;
305 }
306
307 // Validate timing_config
308 if ( ! $this->is_valid_timing_config( $animation_value ) ) {
309 return false;
310 }
311
312 if ( isset( $animation_value['config'] ) && ! $this->is_valid_config_prop( $animation_value ) ) {
313 return false;
314 }
315
316 if ( ! Custom_Effect_Value::is_valid( $animation_value ) ) {
317 return false;
318 }
319
320 return true;
321 }
322
323 private function is_valid_timing_config( $data ) {
324 if ( ! isset( $data['timing_config'] ) || ! is_array( $data['timing_config'] ) ) {
325 return false;
326 }
327
328 $timing = $data['timing_config'];
329
330 if ( ! isset( $timing['$$type'] ) || 'timing-config' !== $timing['$$type'] ) {
331 return false;
332 }
333
334 if ( ! isset( $timing['value'] ) || ! is_array( $timing['value'] ) ) {
335 return false;
336 }
337
338 $timing_value = $timing['value'];
339
340 // Validate duration (accepts both 'number' and 'size' formats)
341 if ( ! $this->is_valid_timing_value( $timing_value, 'duration', 0 ) ) {
342 return false;
343 }
344
345 // Validate delay (accepts both 'number' and 'size' formats)
346 if ( ! $this->is_valid_timing_value( $timing_value, 'delay', 0 ) ) {
347 return false;
348 }
349
350 return true;
351 }
352
353 /**
354 * Validate timing value that can be either 'number' or 'size' type.
355 * - number format: {$$type: 'number', value: 123}
356 * - size format: {$$type: 'size', value: {size: 123, unit: 'ms'}}
357 */
358 private function is_valid_timing_value( $data, $key, $min = null, $max = null ) {
359 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
360 return false;
361 }
362
363 $prop = $data[ $key ];
364
365 if ( ! isset( $prop['$$type'] ) ) {
366 return false;
367 }
368
369 // Accept 'number' format
370 if ( 'number' === $prop['$$type'] ) {
371 return $this->is_valid_number_prop_in_range( $data, $key, $min, $max );
372 }
373
374 // Accept 'size' format
375 if ( 'size' === $prop['$$type'] ) {
376 return $this->is_valid_size_prop_in_range( $data, $key, $min, $max );
377 }
378
379 return false;
380 }
381
382 /**
383 * Validate size prop: {$$type: 'size', value: {size: X, unit: 'ms'}}
384 */
385 private function is_valid_size_prop_in_range( $data, $key, $min = null, $max = null ) {
386 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
387 return false;
388 }
389
390 $prop = $data[ $key ];
391
392 if ( ! isset( $prop['$$type'] ) || 'size' !== $prop['$$type'] ) {
393 return false;
394 }
395
396 if ( ! isset( $prop['value'] ) || ! is_array( $prop['value'] ) ) {
397 return false;
398 }
399
400 if ( ! isset( $prop['value']['size'] ) || ! is_numeric( $prop['value']['size'] ) ) {
401 return false;
402 }
403
404 $value = (float) $prop['value']['size'];
405
406 if ( null !== $min && $value < $min ) {
407 return false;
408 }
409
410 if ( null !== $max && $value > $max ) {
411 return false;
412 }
413
414 return true;
415 }
416 }
417