PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.9
Elementor Website Builder – more than just a page builder v3.35.9
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 3.35.9, at modules/interactions/validation.php

330 lines 8.1 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 if ( ! defined( 'ABSPATH' ) ) {
6 exit; // Exit if accessed directly.
7 }
8
9 class Validation {
10 private $elements_to_interactions_counter = [];
11 private $max_number_of_interactions = 5;
12
13 private const VALID_TRIGGERS = [ 'load', 'scrollIn', 'scrollOut', 'scrollOn' ];
14 private const VALID_EFFECTS = [ 'fade', 'slide', 'scale' ];
15 private const VALID_TYPES = [ 'in', 'out' ];
16 private const VALID_DIRECTIONS = [ '', 'left', 'right', 'top', 'bottom' ];
17
18 public function sanitize( $document ) {
19 return $this->sanitize_document_data( $document );
20 }
21
22 public function validate() {
23 foreach ( $this->elements_to_interactions_counter as $element_id => $number_of_interactions ) {
24 if ( $number_of_interactions > $this->max_number_of_interactions ) {
25 throw new \Exception(
26 sprintf(
27 // translators: %1$s: element ID, %2$d: maximum number of interactions allowed.
28 esc_html__( 'Element %1$s has more than %2$d interactions', 'elementor' ),
29 esc_html( $element_id ),
30 esc_html( $this->max_number_of_interactions )
31 )
32 );
33 }
34 }
35
36 return true;
37 }
38
39 private function sanitize_document_data( $data ) {
40 if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) {
41 $data['elements'] = $this->sanitize_elements_interactions( $data['elements'] );
42 }
43
44 return $data;
45 }
46
47 private function sanitize_elements_interactions( $elements ) {
48 if ( ! is_array( $elements ) ) {
49 return $elements;
50 }
51
52 foreach ( $elements as &$element ) {
53 if ( isset( $element['interactions'] ) ) {
54 $element['interactions'] = $this->sanitize_interactions( $element['interactions'], $element['id'] );
55 }
56
57 if ( isset( $element['elements'] ) && is_array( $element['elements'] ) ) {
58 $element['elements'] = $this->sanitize_elements_interactions( $element['elements'] );
59 }
60 }
61
62 return $elements;
63 }
64
65 private function decode_interactions( $interactions ) {
66 if ( is_array( $interactions ) ) {
67 return isset( $interactions['items'] ) ? $interactions['items'] : [];
68 }
69
70 if ( is_string( $interactions ) ) {
71 $decoded = json_decode( $interactions, true );
72 if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
73 return isset( $decoded['items'] ) ? $decoded['items'] : [];
74 }
75 }
76
77 return [];
78 }
79
80 private function increment_interactions_counter_for( $element_id ) {
81 if ( ! array_key_exists( $element_id, $this->elements_to_interactions_counter ) ) {
82 $this->elements_to_interactions_counter[ $element_id ] = 0;
83 }
84
85 ++$this->elements_to_interactions_counter[ $element_id ];
86
87 return $this;
88 }
89
90 private function sanitize_interactions( $interactions, $element_id ) {
91 $sanitized = [
92 'items' => [],
93 'version' => 1,
94 ];
95
96 $list_of_interactions = $this->decode_interactions( $interactions );
97
98 foreach ( $list_of_interactions as $interaction ) {
99 if ( $this->is_valid_interaction_item( $interaction ) ) {
100 $sanitized['items'][] = $interaction;
101 $this->increment_interactions_counter_for( $element_id );
102 }
103 }
104
105 if ( empty( $sanitized['items'] ) ) {
106 return [];
107 }
108
109 return wp_json_encode( $sanitized );
110 }
111
112 private function is_valid_interaction_item( $item ) {
113 if ( ! is_array( $item ) ) {
114 return false;
115 }
116
117 // Validate PropType format: { $$type: 'interaction-item', value: { ... } }
118 if ( ! isset( $item['$$type'] ) || 'interaction-item' !== $item['$$type'] ) {
119 return false;
120 }
121
122 if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) {
123 return false;
124 }
125
126 $value = $item['value'];
127
128 // Validate required fields exist
129 if ( isset( $value['interaction_id'] ) && ! $this->is_valid_string_prop( $value, 'interaction_id' ) ) {
130 return false;
131 }
132
133 if ( ! $this->is_valid_string_prop( $value, 'trigger', self::VALID_TRIGGERS ) ) {
134 return false;
135 }
136
137 if ( ! $this->is_valid_animation_prop( $value ) ) {
138 return false;
139 }
140
141 return true;
142 }
143
144 private function is_valid_string_prop( $data, $key, $allowed_values = null ) {
145 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
146 return false;
147 }
148
149 $prop = $data[ $key ];
150
151 if ( ! isset( $prop['$$type'] ) || 'string' !== $prop['$$type'] ) {
152 return false;
153 }
154
155 if ( ! isset( $prop['value'] ) || ! is_string( $prop['value'] ) ) {
156 return false;
157 }
158
159 if ( null !== $allowed_values && ! in_array( $prop['value'], $allowed_values, true ) ) {
160 return false;
161 }
162
163 return true;
164 }
165
166 private function is_valid_boolean_prop( $data, $key ) {
167 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
168 return false;
169 }
170
171 $prop = $data[ $key ];
172
173 if ( ! isset( $prop['$$type'] ) || 'boolean' !== $prop['$$type'] ) {
174 return false;
175 }
176
177 if ( ! isset( $prop['value'] ) || ! is_bool( $prop['value'] ) ) {
178 return false;
179 }
180
181 return true;
182 }
183
184 private function is_valid_number_prop( $data, $key ) {
185 if ( ! isset( $data[ $key ] ) || ! is_array( $data[ $key ] ) ) {
186 return false;
187 }
188
189 $prop = $data[ $key ];
190
191 if ( ! isset( $prop['$$type'] ) || 'number' !== $prop['$$type'] ) {
192 return false;
193 }
194
195 if ( ! isset( $prop['value'] ) || ! is_numeric( $prop['value'] ) ) {
196 return false;
197 }
198
199 return true;
200 }
201
202 private function is_valid_number_prop_in_range( $data, $key, $min = null, $max = null ) {
203 if ( ! $this->is_valid_number_prop( $data, $key ) ) {
204 return false;
205 }
206
207 $value = (float) $data[ $key ]['value'];
208
209 if ( null !== $min && $value < $min ) {
210 return false;
211 }
212
213 if ( null !== $max && $value > $max ) {
214 return false;
215 }
216
217 return true;
218 }
219
220 private function is_valid_config_prop( $data ) {
221 if ( ! isset( $data['config'] ) || ! is_array( $data['config'] ) ) {
222 return false;
223 }
224
225 $config = $data['config'];
226
227 if ( ! isset( $config['$$type'] ) || 'config' !== $config['$$type'] ) {
228 return false;
229 }
230
231 if ( ! isset( $config['value'] ) || ! is_array( $config['value'] ) ) {
232 return false;
233 }
234
235 $config_value = $config['value'];
236
237 if ( isset( $config_value['replay'] ) && ! $this->is_valid_boolean_prop( $config_value, 'replay' ) ) {
238 return false;
239 }
240
241 if ( isset( $config_value['relativeTo'] ) && ! $this->is_valid_string_prop( $config_value, 'relativeTo' ) ) {
242 return false;
243 }
244
245 if ( isset( $config_value['offsetTop'] ) && ! $this->is_valid_number_prop_in_range( $config_value, 'offsetTop', 0, 100 ) ) {
246 return false;
247 }
248
249 if ( isset( $config_value['offsetBottom'] ) && ! $this->is_valid_number_prop_in_range( $config_value, 'offsetBottom', 0, 100 ) ) {
250 return false;
251 }
252
253 return true;
254 }
255
256 private function is_valid_animation_prop( $data ) {
257 if ( ! isset( $data['animation'] ) || ! is_array( $data['animation'] ) ) {
258 return false;
259 }
260
261 $animation = $data['animation'];
262
263 if ( ! isset( $animation['$$type'] ) || 'animation-preset-props' !== $animation['$$type'] ) {
264 return false;
265 }
266
267 if ( ! isset( $animation['value'] ) || ! is_array( $animation['value'] ) ) {
268 return false;
269 }
270
271 $animation_value = $animation['value'];
272
273 // Validate effect
274 if ( ! $this->is_valid_string_prop( $animation_value, 'effect', self::VALID_EFFECTS ) ) {
275 return false;
276 }
277
278 // Validate type
279 if ( ! $this->is_valid_string_prop( $animation_value, 'type', self::VALID_TYPES ) ) {
280 return false;
281 }
282
283 // Validate direction (can be empty string)
284 if ( ! $this->is_valid_string_prop( $animation_value, 'direction', self::VALID_DIRECTIONS ) ) {
285 return false;
286 }
287
288 // Validate timing_config
289 if ( ! $this->is_valid_timing_config( $animation_value ) ) {
290 return false;
291 }
292
293 if ( isset( $animation_value['config'] ) && ! $this->is_valid_config_prop( $animation_value ) ) {
294 return false;
295 }
296
297 return true;
298 }
299
300 private function is_valid_timing_config( $data ) {
301 if ( ! isset( $data['timing_config'] ) || ! is_array( $data['timing_config'] ) ) {
302 return false;
303 }
304
305 $timing = $data['timing_config'];
306
307 if ( ! isset( $timing['$$type'] ) || 'timing-config' !== $timing['$$type'] ) {
308 return false;
309 }
310
311 if ( ! isset( $timing['value'] ) || ! is_array( $timing['value'] ) ) {
312 return false;
313 }
314
315 $timing_value = $timing['value'];
316
317 // Validate duration
318 if ( ! $this->is_valid_number_prop( $timing_value, 'duration' ) ) {
319 return false;
320 }
321
322 // Validate delay
323 if ( ! $this->is_valid_number_prop( $timing_value, 'delay' ) ) {
324 return false;
325 }
326
327 return true;
328 }
329 }
330