diff --git a/modules/interactions/module.php b/modules/interactions/module.php index 3a8a44126ff3..430cd36f7028 100644 --- a/modules/interactions/module.php +++ b/modules/interactions/module.php @@ -58,18 +58,18 @@ public function __construct() { add_action( 'elementor/preview/enqueue_scripts', fn () => $this->enqueue_preview_scripts() ); add_action( 'elementor/editor/after_enqueue_scripts', fn () => $this->enqueue_editor_scripts() ); - // add_filter( 'elementor/document/save/data', - // /** - // * @throws \Exception - // */ - // function( $document ) { - // $validation = new Validation( $this->get_presets() ); - // $document_after_sanitization = $validation->sanitize( $document ); - // $validation->validate(); - - // return $document_after_sanitization; - // }, - // 10, 1 ); + add_filter( 'elementor/document/save/data', + /** + * @throws \Exception + */ + function( $document ) { + $validation = new Validation( $this->get_presets() ); + $document_after_sanitization = $validation->sanitize( $document ); + $validation->validate(); + + return $document_after_sanitization; + }, + 10, 1 ); } private function get_config() { diff --git a/modules/interactions/validation.php b/modules/interactions/validation.php index a75a7661660e..6ab4ad90c77b 100644 --- a/modules/interactions/validation.php +++ b/modules/interactions/validation.php @@ -7,12 +7,18 @@ } class Validation { - private $valid_ids = []; + private $valid_triggers = [ 'load', 'scrollIn', 'scrollOut' ]; + private $valid_effects = [ 'fade', 'slide', 'scale' ]; + private $valid_types = [ 'in', 'out' ]; + private $valid_directions = [ '', 'left', 'right', 'top', 'bottom' ]; + private $valid_durations = [ 0, 100, 200, 300, 400, 500, 750, 1000, 1250, 1500 ]; + private $valid_delays = [ 0, 100, 200, 300, 400, 500, 750, 1000, 1250, 1500 ]; + private $elements_to_interactions_counter = []; private $max_number_of_interactions = 5; public function __construct( Presets $presets ) { - $this->valid_ids = array_column( $presets->list(), 'value' ); + // Presets no longer needed with new structure validation } public function sanitize( $document ) { @@ -80,16 +86,10 @@ private function sanitize_interactions( $interactions, $element_id ) { } foreach ( $list_of_interactions as $interaction ) { - $animation_id = null; - - if ( is_string( $interaction ) ) { - $animation_id = $interaction; - } elseif ( is_array( $interaction ) && isset( $interaction['animation']['animation_id'] ) ) { - $animation_id = $interaction['animation']['animation_id']; - } - - if ( $animation_id && $this->is_valid_animation_id( $animation_id ) ) { - $sanitized['items'][] = $interaction; + $sanitized_interaction = $this->sanitize_interaction_item( $interaction ); + + if ( $sanitized_interaction !== null ) { + $sanitized['items'][] = $sanitized_interaction; if ( ! array_key_exists( $element_id, $this->elements_to_interactions_counter ) ) { $this->elements_to_interactions_counter[ $element_id ] = 0; } @@ -101,20 +101,130 @@ private function sanitize_interactions( $interactions, $element_id ) { return []; } - return wp_json_encode( $sanitized ); + return $sanitized; } - private function is_valid_animation_id( $animation_id ) { - if ( ! is_string( $animation_id ) || empty( $animation_id ) ) { - return false; + private function sanitize_interaction_item( $interaction ) { + if ( ! is_array( $interaction ) ) { + return null; + } + + // Unwrap if wrapped with $$type: 'interaction-item' + $item_value = $interaction; + if ( isset( $interaction['$$type'] ) && $interaction['$$type'] === 'interaction-item' && isset( $interaction['value'] ) ) { + $item_value = $interaction['value']; + } + + // Extract and validate values + $interaction_id = $this->get_prop_value( $item_value, 'interaction_id' ); + $trigger = $this->get_prop_value( $item_value, 'trigger' ); + $animation = $this->get_prop_value( $item_value, 'animation' ); + + // Skip temp IDs - backend will generate real ones + if ( $this->is_temp_id( $interaction_id ) ) { + $interaction_id = ''; + } + + if ( empty( $trigger ) || empty( $animation ) ) { + return null; + } + + $effect = $this->get_prop_value( $animation, 'effect' ); + $type = $this->get_prop_value( $animation, 'type' ); + $direction = $this->get_prop_value( $animation, 'direction' ); + $timing_config = $this->get_prop_value( $animation, 'timing_config' ); + + // Validate required fields + if ( ! $this->is_valid_trigger( $trigger ) || ! $this->is_valid_effect( $effect ) || ! $this->is_valid_type( $type ) ) { + return null; + } + + // Validate optional fields + if ( ! empty( $direction ) && ! $this->is_valid_direction( $direction ) ) { + $direction = ''; } - $sanitized_id = sanitize_text_field( $animation_id ); + // Extract and validate timing + $duration = 300; + $delay = 0; + if ( is_array( $timing_config ) ) { + $duration = $this->get_prop_value( $timing_config, 'duration', 300 ); + $delay = $this->get_prop_value( $timing_config, 'delay', 0 ); + } - if ( $sanitized_id !== $animation_id ) { - return false; + if ( ! $this->is_valid_duration( $duration ) ) { + $duration = 300; + } + if ( ! $this->is_valid_delay( $delay ) ) { + $delay = 0; } - return in_array( $animation_id, $this->valid_ids, true ); + // Build sanitized item value + $sanitized_value = [ + 'interaction_id' => $this->create_prop_value( 'string', $interaction_id ), + 'trigger' => $this->create_prop_value( 'string', $trigger ), + 'animation' => $this->create_prop_value( 'animation-preset-props', [ + 'effect' => $this->create_prop_value( 'string', $effect ), + 'type' => $this->create_prop_value( 'string', $type ), + 'direction' => $this->create_prop_value( 'string', $direction ), + 'timing_config' => $this->create_prop_value( 'timing-config', [ + 'duration' => $this->create_prop_value( 'number', (int) $duration ), + 'delay' => $this->create_prop_value( 'number', (int) $delay ), + ] ), + ] ), + ]; + + // Wrap with $$type: 'interaction-item' + return $this->create_prop_value( 'interaction-item', $sanitized_value ); + } + + private function get_prop_value( $data, $key, $default = '' ) { + if ( ! is_array( $data ) || ! isset( $data[ $key ] ) ) { + return $default; + } + + $value = $data[ $key ]; + + // Handle TransformablePropValue structure: { $$type: 'string', value: 'actual-value' } + if ( is_array( $value ) && isset( $value['$$type'] ) && isset( $value['value'] ) ) { + return $value['value']; + } + + return $value !== null ? $value : $default; + } + + private function create_prop_value( $type, $value ) { + return [ + '$$type' => $type, + 'value' => $value, + ]; + } + + private function is_temp_id( $id ) { + return is_string( $id ) && strpos( $id, 'temp-' ) === 0; + } + + private function is_valid_trigger( $trigger ) { + return in_array( $trigger, $this->valid_triggers, true ); + } + + private function is_valid_effect( $effect ) { + return in_array( $effect, $this->valid_effects, true ); + } + + private function is_valid_type( $type ) { + return in_array( $type, $this->valid_types, true ); + } + + private function is_valid_direction( $direction ) { + return in_array( $direction, $this->valid_directions, true ); + } + + private function is_valid_duration( $duration ) { + return is_numeric( $duration ) && in_array( (int) $duration, $this->valid_durations, true ); + } + + private function is_valid_delay( $delay ) { + return is_numeric( $delay ) && in_array( (int) $delay, $this->valid_delays, true ); } }