| @@ -1,25 +1,20 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace Elementor\Modules\Interactions; |
| 4 | 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 | 5 | if ( ! defined( 'ABSPATH' ) ) { |
| 11 | 6 | exit; // Exit if accessed directly. |
| 12 | 7 | } |
| 13 | 8 | |
| 14 | 9 | class Validation { |
| 10 | + private $valid_ids = []; | |
| 15 | 11 | private $elements_to_interactions_counter = []; |
| 16 | 12 | private $max_number_of_interactions = 5; |
| 17 | 13 | |
| 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' ]; | |
| 14 | + public function __construct( Presets $presets ) { | |
| 15 | + $this->valid_ids = array_column( $presets->list(), 'value' ); | |
| 16 | + } | |
| 22 | 17 | |
| 23 | 18 | public function sanitize( $document ) { |
| 24 | 19 | return $this->sanitize_document_data( $document ); |
| 25 | 20 | } |
| @@ -28,9 +23,9 @@ | ||
| 28 | 23 | foreach ( $this->elements_to_interactions_counter as $element_id => $number_of_interactions ) { |
| 29 | 24 | if ( $number_of_interactions > $this->max_number_of_interactions ) { |
| 30 | 25 | throw new \Exception( |
| 31 | 26 | sprintf( |
| 32 | - // translators: %1$s: element ID, %2$d: maximum number of interactions allowed. | |
| 27 | + // translators: %1 is the element ID and %2 is the maximum number of interactions | |
| 33 | 28 | esc_html__( 'Element %1$s has more than %2$d interactions', 'elementor' ), |
| 34 | 29 | esc_html( $element_id ), |
| 35 | 30 | esc_html( $this->max_number_of_interactions ) |
| 36 | 31 | ) |
| @@ -68,11 +63,8 @@ | ||
| 68 | 63 | } |
| 69 | 64 | |
| 70 | 65 | private function decode_interactions( $interactions ) { |
| 71 | 66 | 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 | 67 | return isset( $interactions['items'] ) ? $interactions['items'] : []; |
| 76 | 68 | } |
| 77 | 69 | |
| 78 | 70 | if ( is_string( $interactions ) ) { |
| @@ -77,11 +69,8 @@ | ||
| 77 | 69 | |
| 78 | 70 | if ( is_string( $interactions ) ) { |
| 79 | 71 | $decoded = json_decode( $interactions, true ); |
| 80 | 72 | 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 | 73 | return isset( $decoded['items'] ) ? $decoded['items'] : []; |
| 85 | 74 | } |
| 86 | 75 | } |
| 87 | 76 | |
| @@ -106,9 +95,17 @@ | ||
| 106 | 95 | |
| 107 | 96 | $list_of_interactions = $this->decode_interactions( $interactions ); |
| 108 | 97 | |
| 109 | 98 | foreach ( $list_of_interactions as $interaction ) { |
| 110 | - if ( $this->is_valid_interaction_item( $interaction ) ) { | |
| 99 | + $animation_id = null; | |
| 100 | + | |
| 101 | + if ( is_string( $interaction ) ) { | |
| 102 | + $animation_id = $interaction; | |
| 103 | + } elseif ( is_array( $interaction ) && isset( $interaction['animation']['animation_id'] ) ) { | |
| 104 | + $animation_id = $interaction['animation']['animation_id']; | |
| 105 | + } | |
| 106 | + | |
| 107 | + if ( $animation_id && $this->is_valid_animation_id( $animation_id ) ) { | |
| 111 | 108 | $sanitized['items'][] = $interaction; |
| 112 | 109 | $this->increment_interactions_counter_for( $element_id ); |
| 113 | 110 | } |
| 114 | 111 | } |
| @@ -119,298 +116,18 @@ | ||
| 119 | 116 | |
| 120 | 117 | return wp_json_encode( $sanitized ); |
| 121 | 118 | } |
| 122 | 119 | |
| 123 | - private function is_valid_interaction_item( $item ) { | |
| 124 | - if ( ! is_array( $item ) ) { | |
| 120 | + private function is_valid_animation_id( $animation_id ) { | |
| 121 | + if ( ! is_string( $animation_id ) || empty( $animation_id ) ) { | |
| 125 | 122 | return false; |
| 126 | 123 | } |
| 127 | 124 | |
| 128 | - // Validate PropType format: { $$type: 'interaction-item', value: { ... } } | |
| 129 | - if ( ! isset( $item['$$type'] ) || 'interaction-item' !== $item['$$type'] ) { | |
| 130 | - return false; | |
| 131 | - } | |
| 125 | + $sanitized_id = sanitize_text_field( $animation_id ); | |
| 132 | 126 | |
| 133 | - if ( ! isset( $item['value'] ) || ! is_array( $item['value'] ) ) { | |
| 127 | + if ( $sanitized_id !== $animation_id ) { | |
| 134 | 128 | return false; |
| 135 | 129 | } |
| 136 | 130 | |
| 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; | |
| 131 | + return in_array( $animation_id, $this->valid_ids, true ); | |
| 415 | 132 | } |
| 416 | 133 | } |