From dd8db2fec27f5c3e701fb43befa002be6617216d Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Wed, 22 Oct 2025 11:40:01 +0800 Subject: [PATCH 01/21] fix: downgrading and upgrading multiple times should migrate --- interactions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interactions.php b/interactions.php index 081bfa0..832f4c8 100644 --- a/interactions.php +++ b/interactions.php @@ -31,8 +31,8 @@ function interact_on_activation() { // Run migration if version not set or outdated if ( ! $saved_version || version_compare( $saved_version, INTERACT_VERSION, '<' ) ) { do_action( 'interact/on_plugin_update', $saved_version, INTERACT_VERSION ); - update_option( 'interact_plugin_version', INTERACT_VERSION ); } + update_option( 'interact_plugin_version', INTERACT_VERSION ); } } register_activation_hook( __FILE__, 'interact_on_activation' ); From b50e53b5a622bc56cc3cf25e6085642a8c78646a Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Sun, 26 Oct 2025 16:12:20 +0800 Subject: [PATCH 02/21] chore: updated readme --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 481eb62..95e379c 100644 --- a/readme.txt +++ b/readme.txt @@ -146,10 +146,10 @@ https://github.com/gambitph/Interactions = 1.3.0 = * New: Interaction library -* New: Initial release in the WordPress Plugin Directory! * New: Block name field is now searchable #70 * New: Import / export functionality #71 * New: Box shadow action #81 +* New: 3D Rotate - new transform origin option * Fixed: Hover interaction glitches when hovering too fast #9 * Fixed: On enter viewport doesn't always trigger when on mobile #23 * Fixed: Confetti action - selecting window will no longer show a display target warning message #74 From f4e4cd0a28567c2e5011a3fe72793ec08771683a Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Mon, 27 Oct 2025 23:55:45 +0800 Subject: [PATCH 03/21] chore: dont' exclude pro in search --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0f52398..ec4b8c8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,5 +40,8 @@ "editor.codeActionsOnSave": { "source.fixAll.stylelint": "never" } + }, + "search.exclude": { + "**/pro__premium_only": false } } \ No newline at end of file From fa36321a89df2e4272fae06d60df4812b33a9e8d Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Tue, 28 Oct 2025 13:16:30 +0800 Subject: [PATCH 04/21] build: added file renaming for premium build --- scripts/package.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/package.js b/scripts/package.js index 86f080a..473457e 100644 --- a/scripts/package.js +++ b/scripts/package.js @@ -312,6 +312,16 @@ async function packagePlugin() { } } + // Rename interactions.php to plugin.php for premium builds only + if ( IS_PREMIUM_BUILD ) { + const oldPath = path.join( BUILD_DIR, 'interactions.php' ) + const newPath = path.join( BUILD_DIR, 'plugin.php' ) + if ( fs.existsSync( oldPath ) ) { + fs.renameSync( oldPath, newPath ) + console.log( 'π Renamed interactions.php to plugin.php for premium build' ) + } + } + console.log( 'π Copying source directories...' ) // Pass isSrcRoot = true for the top-level src folder copyDir( 'src', path.join( BUILD_DIR, 'src' ), true ) From 504b5a6b846acf30a6beff700ac96734ce88baee Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Sat, 1 Nov 2025 11:15:24 +0800 Subject: [PATCH 05/21] chore: updated readme screenshots --- readme.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index 95e379c..d92095c 100644 --- a/readme.txt +++ b/readme.txt @@ -133,8 +133,9 @@ The free version includes basic animations and interactions. Premium adds advanc == Screenshots == -1. Interaction Library β Pre-built animations and effects. +1. Adding from the Interaction Library β Pre-built animations and effects. 2. Advanced trigger and action timeline builder β Create custom interactions with flexible logic and multiple steps. +3. Interaction Library contents β Pre-built animations and effects. == Source == From 7af089b8828160f93f5045063d93ef8daccaa9fb Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Sat, 1 Nov 2025 11:37:45 +0800 Subject: [PATCH 06/21] chore: updated plugin name to be more descriptive --- interactions.php | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interactions.php b/interactions.php index 832f4c8..32e78c7 100644 --- a/interactions.php +++ b/interactions.php @@ -1,6 +1,6 @@ Date: Sat, 1 Nov 2025 11:40:22 +0800 Subject: [PATCH 07/21] chore: updated description --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index df79741..726cacb 100644 --- a/readme.txt +++ b/readme.txt @@ -12,7 +12,7 @@ Add animations and interactivity to your blocks. Choose from ready-made effects == Description == -**Interactions β WordPress Animations, Effects & Functionality for Gutenberg Blocks** +**Interactions β WordPress Animations, Interactive Experiences for Gutenberg Blocks** Want to make your website feel alive and interactive? **Interactions** is the easiest way to add animations, effects, interactivity, and functional features to WordPress β directly inside the block editor. From c69d4c7964681d1105b016761810da8b38b26fd8 Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Thu, 27 Nov 2025 14:45:21 +0800 Subject: [PATCH 08/21] fix: conflict with others that use freemius activation --- src/freemius.php | 98 ++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/freemius.php b/src/freemius.php index 5dec256..f0a1434 100644 --- a/src/freemius.php +++ b/src/freemius.php @@ -601,7 +601,7 @@ function __interact_mask_license_key( $key ) {
-
+
@@ -610,68 +610,70 @@ function __interact_mask_license_key( $key ) {
LINK;
From 5b3af2bca3e85008fb03a78a77b7e738ffec78e6 Mon Sep 17 00:00:00 2001
From: "bfintal@gmail.com" <>
Date: Sat, 29 Nov 2025 19:07:32 +0800
Subject: [PATCH 09/21] chore: updated readme
---
readme.txt | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/readme.txt b/readme.txt
index 726cacb..4efcd33 100644
--- a/readme.txt
+++ b/readme.txt
@@ -98,6 +98,11 @@ Create custom interactions easily with a simple Trigger β Action builder. Feat
- **Regular Updates** β New features and improvements
- **Commercial License** β Use in client projects
+**Source Code:**
+
+The source code for this plugin is available on GitHub:
+https://github.com/gambitph/Interactions
+
== Installation ==
1. Install βInteractionsβ from the WordPress Plugin Directory, or upload it to `/wp-content/plugins/interactions/`.
@@ -137,10 +142,7 @@ The free version includes basic animations and interactions. Premium adds advanc
2. Advanced trigger and action timeline builder β Create custom interactions with flexible logic and multiple steps.
3. Interaction Library contents β Pre-built animations and effects.
-== Source ==
-
-The source code for this plugin is available on GitHub:
-https://github.com/gambitph/Interactions
+== Upgrade Notice ==
== Changelog ==
From 5e7aacd5bcdccb51bda1cbbcad2153b4a4b5f6f8 Mon Sep 17 00:00:00 2001
From: "bfintal@gmail.com" <>
Date: Sat, 29 Nov 2025 19:50:50 +0800
Subject: [PATCH 10/21] chore: updated readme
---
readme.txt | 23 +++++++++++++++--------
1 file changed, 15 insertions(+), 8 deletions(-)
diff --git a/readme.txt b/readme.txt
index 4efcd33..121b464 100644
--- a/readme.txt
+++ b/readme.txt
@@ -12,22 +12,23 @@ Add animations and interactivity to your blocks. Choose from ready-made effects
== Description ==
-**Interactions β WordPress Animations, Interactive Experiences for Gutenberg Blocks**
+**Interactions β WordPress Animations, Interactive Experiences for Gutenberg Blocks**
-Want to make your website feel alive and interactive? **Interactions** is the easiest way to add animations, effects, interactivity, and functional features to WordPress β directly inside the block editor.
+[Visit our website](https://wpinteractions.com) to learn more about how Interactions work.
+
+Want to make your website feel alive and interactive? **Interactions** is the easiest way to add animations, effects, interactivity, and functional features to WordPress β directly inside the block editor. Check our [samples page here](https://wpinteractions.com/samples/) to see a glimpse of what type of interactions you can create.
You don't need coding skills or complex tools. With Interactions, you can:
-- **Pick from the Interactions Library** β A collection of pre-built animations and effects (like images that move upon scrolling down the page, buttons that glow when hovered, and more). Just click and apply.
-- **Build your own custom effects** β Use a simple **Trigger β Action** system. Example: "On scroll β Fade in block", or "On click β Play video".
-- **Add functional features** β Securely update post data, handle form submissions, display user info, copy text to clipboard, and more without coding.
+- **Pick from the [Interactions Library](https://docs.wpinteractions.com/article/744-how-to-use-interactions-library)** β A collection of pre-built animations and effects (like images that move upon scrolling down the page, buttons that glow when hovered, and more). Just click and apply. [Learn more](https://docs.wpinteractions.com/article/744-how-to-use-interactions-library)
+- **Build your own custom effects** β Use a simple **Trigger β Action** system. Example: "On scroll β Fade in block", or "On click β Play video". [Learn more](https://docs.wpinteractions.com/article/577-what-is-wp-interactions-and-how-does-it-work)
+- **Add functional features** β Securely update post data, handle form submissions, display user info, copy text to clipboard, and more without coding. [Learn more](https://docs.wpinteractions.com/category/729-interactions)
-Whether you want subtle hover effects, attention-grabbing story-telling animations, playful micro-interactions, or powerful functional features, Interactions makes it possible.
+Whether you want subtle hover effects, attention-grabbing story-telling animations, playful micro-interactions, or powerful functional features, [Interactions](https://wpinteractions.com) makes it possible.
### π Features
-Create custom interactions easily with a simple Trigger β Action builder. Features include:
-
+Create [custom interactions](https://docs.wpinteractions.com/article/571-what-are-interactions) easily with a simple Trigger β Action builder. Features include:
**Animations & Visual Effects:**
@@ -70,6 +71,8 @@ Create custom interactions easily with a simple Trigger β Action builder. Feat
### π What's in Premium?
+[Check our pricing page](https://wpinteractions.com/pricing/) to learn more about what's in Interactions premium.
+
**Advanced Interactions:**
- **Scroll Strength** β Measure scroll intensity
@@ -146,6 +149,10 @@ The free version includes basic animations and interactions. Premium adds advanc
== Changelog ==
+= 1.3.1 =
+
+* Fixed: License activation issue
+
= 1.3.0 =
* New: Interaction library
From 0c397bd372b8554eba10125f719070348c3c01e3 Mon Sep 17 00:00:00 2001
From: "bfintal@gmail.com" <>
Date: Sat, 29 Nov 2025 19:51:56 +0800
Subject: [PATCH 11/21] chore: version bumped to 1.3.1
---
interactions.php | 4 ++--
package.json | 2 +-
readme.txt | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/interactions.php b/interactions.php
index 32e78c7..39107bb 100644
--- a/interactions.php
+++ b/interactions.php
@@ -7,7 +7,7 @@
* Author URI: http://gambit.ph
* License: GPLv2 or later
* Text Domain: interactions
- * Version: 1.3.0
+ * Version: 1.3.1
*
* @fs_premium_only /freemius.php, /freemius/
*/
@@ -18,7 +18,7 @@
}
defined( 'INTERACT_BUILD' ) || define( 'INTERACT_BUILD', 'free' );
-defined( 'INTERACT_VERSION' ) || define( 'INTERACT_VERSION', '1.3.0' );
+defined( 'INTERACT_VERSION' ) || define( 'INTERACT_VERSION', '1.3.1' );
defined( 'INTERACT_FILE' ) || define( 'INTERACT_FILE', __FILE__ );
/**
diff --git a/package.json b/package.json
index b7d3cc5..2410370 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "interactions",
- "version": "1.3.0",
+ "version": "1.3.1",
"description": "Make your blocks interactive! Effortlessly set triggers that do actions",
"author": "Benjamin Intal of Gambit",
"private": true,
diff --git a/readme.txt b/readme.txt
index 121b464..dc92690 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: interaction, interactivity, trigger, blocks, gutenberg
Requires at least: 6.6.4
Tested up to: 6.8.3
Requires PHP: 8.0
-Stable tag: 1.3.0
+Stable tag: 1.3.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
From 0c29260cfa63435980c316138551ac67f6bab049 Mon Sep 17 00:00:00 2001
From: "bfintal@gmail.com" <>
Date: Sat, 29 Nov 2025 20:04:45 +0800
Subject: [PATCH 12/21] chore: updated plugin name and readme info
---
interactions.php | 2 +-
readme.txt | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/interactions.php b/interactions.php
index 39107bb..1a98eb5 100644
--- a/interactions.php
+++ b/interactions.php
@@ -1,6 +1,6 @@
Date: Thu, 4 Dec 2025 01:30:32 +0800
Subject: [PATCH 13/21] chore: updated tested up to
---
readme.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/readme.txt b/readme.txt
index 079c43f..7b14e02 100644
--- a/readme.txt
+++ b/readme.txt
@@ -2,7 +2,7 @@
Contributors: bfintal, gambitph
Tags: interaction, interactivity, trigger, blocks, gutenberg
Requires at least: 6.6.4
-Tested up to: 6.8.3
+Tested up to: 6.9
Requires PHP: 8.0
Stable tag: 1.3.1
License: GPLv2 or later
From 65772831a37943a01819d193477931d7d4d0742d Mon Sep 17 00:00:00 2001
From: "bfintal@gmail.com" <>
Date: Fri, 5 Dec 2025 09:20:51 +0800
Subject: [PATCH 14/21] Added sanitization and security for users without
unfiltered_html capability
---
src/action-types/abstract-action-type.php | 32 ++++++++
.../class-action-type-background-color.php | 5 ++
.../class-action-type-background-image.php | 5 ++
.../class-action-type-css-rule.php | 5 ++
.../class-action-type-display.php | 23 ++++++
src/action-types/class-action-type-move.php | 15 ++++
.../class-action-type-opacity.php | 11 +++
.../class-action-type-redirect-to-url.php | 11 +++
src/action-types/class-action-type-rotate.php | 31 ++++++++
src/action-types/class-action-type-scale.php | 18 +++++
src/action-types/class-action-type-skew.php | 18 +++++
.../class-action-type-text-color.php | 7 ++
.../class-action-type-toggle-class.php | 31 ++++++--
.../class-action-type-toggle-video.php | 17 +++++
.../class-action-type-update-attribute.php | 74 +++++++++++++++++++
.../class-action-type-visibility.php | 10 +++
src/class-interaction.php | 44 ++++++++++-
.../components/timeline/property-control.js | 26 ++++++-
src/editor/editor.php | 1 +
19 files changed, 373 insertions(+), 11 deletions(-)
diff --git a/src/action-types/abstract-action-type.php b/src/action-types/abstract-action-type.php
index e84a7a7..6753a9c 100644
--- a/src/action-types/abstract-action-type.php
+++ b/src/action-types/abstract-action-type.php
@@ -256,5 +256,37 @@ public function initilize_action( $action, $animation_data ) {
return $action;
}
+
+ /**
+ * Sanitizes the action's value before saving.
+ *
+ * Override this in a child class to implement specific sanitization.
+ *
+ * @param mixed $value The action value to sanitize.
+ * @return mixed The sanitized action value.
+ */
+ public function sanitize_data_for_saving( $value ) {
+ // By default, no sanitization is applied.
+ return $value;
+ }
+
+ /**
+ * Remove any `expression(...)` and `javascript:` content from a CSS style string for security.
+ *
+ * @param string $string
+ * @return string
+ */
+ public function sanitize_style_value( $string ) {
+ if ( ! is_string( $string ) ) {
+ return $string;
+ }
+ // Remove all expression(...) (case-insensitive).
+ $string = preg_replace( '/expression\s*\((?:[^\(\)]|(?R))*\)/i', '', $string );
+
+ // Remove all javascript: URIs (case-insensitive).
+ $string = preg_replace( '/javascript\s*:/i', '', $string );
+
+ return $string;
+ }
}
}
diff --git a/src/action-types/class-action-type-background-color.php b/src/action-types/class-action-type-background-color.php
index 4b02e78..b905778 100644
--- a/src/action-types/class-action-type-background-color.php
+++ b/src/action-types/class-action-type-background-color.php
@@ -52,6 +52,11 @@ public function initialize() {
// return parent::initilize_action( $action, $animation_data );
// }
+
+ public function sanitize_data_for_saving( $value ) {
+ $value['color'] = $this->sanitize_style_value( $value['color'] );
+ return $value;
+ }
}
interact_add_action_type( 'backgroundColor', 'Interact_Action_Type_Background_Color' );
diff --git a/src/action-types/class-action-type-background-image.php b/src/action-types/class-action-type-background-image.php
index 4187202..0d93b0a 100644
--- a/src/action-types/class-action-type-background-image.php
+++ b/src/action-types/class-action-type-background-image.php
@@ -33,6 +33,11 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ $value['image'] = $this->sanitize_style_value( $value['image'] );
+ return $value;
+ }
}
interact_add_action_type( 'backgroundImage', 'Interact_Action_Type_Background_Image' );
diff --git a/src/action-types/class-action-type-css-rule.php b/src/action-types/class-action-type-css-rule.php
index ad8a184..397c899 100644
--- a/src/action-types/class-action-type-css-rule.php
+++ b/src/action-types/class-action-type-css-rule.php
@@ -40,6 +40,11 @@ public function initialize() {
],
];
}
+
+ public function sanitize_data_for_saving( $value ) {
+ $value['value'] = $this->sanitize_style_value( $value['value'] );
+ return $value;
+ }
}
interact_add_action_type( 'cssRule', 'Interact_Action_Type_Css_Rule' );
diff --git a/src/action-types/class-action-type-display.php b/src/action-types/class-action-type-display.php
index c3aac36..6f1e839 100644
--- a/src/action-types/class-action-type-display.php
+++ b/src/action-types/class-action-type-display.php
@@ -55,6 +55,29 @@ public function initialize() {
$this->has_duration = false;
$this->has_easing = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['display'] ) ) {
+ $allowed_values = [
+ 'block',
+ 'none',
+ 'inline',
+ 'inline-block',
+ 'flex',
+ 'inline-flex',
+ 'grid',
+ 'inline-grid',
+ 'initial',
+ 'inherit',
+ 'revert',
+ 'unset',
+ ];
+ if ( ! in_array( $value['display'], $allowed_values, true ) ) {
+ $value['display'] = 'block';
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'display', 'Interact_Action_Type_Display' );
diff --git a/src/action-types/class-action-type-move.php b/src/action-types/class-action-type-move.php
index 0b277ed..a17a8fd 100644
--- a/src/action-types/class-action-type-move.php
+++ b/src/action-types/class-action-type-move.php
@@ -58,6 +58,21 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ // Ensure x, y, z are sanitized as numeric (including negatives and decimals), otherwise set to null (but leave blank as is)
+ foreach ( [ 'x', 'y', 'z' ] as $key ) {
+ if ( isset( $value[ $key ] ) && $value[ $key ] !== '' ) {
+ // Allow negative/positive/decimal
+ if ( is_numeric( $value[ $key ] ) ) {
+ $value[ $key ] = $value[ $key ] + 0; // Cast to int or float
+ } else {
+ $value[ $key ] = null;
+ }
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'move', 'Interact_Action_Type_Move' );
diff --git a/src/action-types/class-action-type-opacity.php b/src/action-types/class-action-type-opacity.php
index 4a496aa..8284f4c 100644
--- a/src/action-types/class-action-type-opacity.php
+++ b/src/action-types/class-action-type-opacity.php
@@ -36,6 +36,17 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['opacity'] ) ) {
+ if ( is_numeric( $value['opacity'] ) ) {
+ $value['opacity'] = $value['opacity'] + 0;
+ } else {
+ $value['opacity'] = null;
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'opacity', 'Interact_Action_Type_Opacity' );
diff --git a/src/action-types/class-action-type-redirect-to-url.php b/src/action-types/class-action-type-redirect-to-url.php
index 93360f9..acc7833 100644
--- a/src/action-types/class-action-type-redirect-to-url.php
+++ b/src/action-types/class-action-type-redirect-to-url.php
@@ -37,6 +37,17 @@ public function initialize() {
$this->has_easing = false;
$this->has_preview = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['url'] ) ) {
+ if ( is_string( $value['url'] ) ) {
+ $value['url'] = esc_url( $value['url'] );
+ } else {
+ $value['url'] = null;
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'redirectToUrl', 'Interact_Action_Type_Redirect_To_Url' );
diff --git a/src/action-types/class-action-type-rotate.php b/src/action-types/class-action-type-rotate.php
index 2a74918..d4dc818 100644
--- a/src/action-types/class-action-type-rotate.php
+++ b/src/action-types/class-action-type-rotate.php
@@ -63,6 +63,37 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['rotate'] ) ) {
+ if ( is_numeric( $value['rotate'] ) ) {
+ $value['rotate'] = $value['rotate'] + 0;
+ } else {
+ $value['rotate'] = null;
+ }
+ }
+
+ if ( is_array( $value ) && isset( $value['transformOrigin'] ) ) {
+ $allowed_transform_origins = [
+ 'center',
+ 'top',
+ 'right',
+ 'bottom',
+ 'left',
+ 'top left',
+ 'top right',
+ 'bottom left',
+ 'bottom right',
+ 'custom',
+ ];
+ if ( ! in_array( $value['transformOrigin'], $allowed_transform_origins, true ) ) {
+ $value['transformOrigin'] = 'center';
+ }
+ }
+
+ $value['customTransformOrigin'] = $this->sanitize_style_value( $value['customTransformOrigin'] );
+ return $value;
+ }
}
interact_add_action_type( 'rotate', 'Interact_Action_Type_Rotate' );
diff --git a/src/action-types/class-action-type-scale.php b/src/action-types/class-action-type-scale.php
index 60748f0..b83b61e 100644
--- a/src/action-types/class-action-type-scale.php
+++ b/src/action-types/class-action-type-scale.php
@@ -46,6 +46,24 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['x'] ) ) {
+ if ( is_numeric( $value['x'] ) ) {
+ $value['x'] = $value['x'] + 0;
+ } else {
+ $value['x'] = null;
+ }
+ }
+ if ( is_array( $value ) && isset( $value['y'] ) ) {
+ if ( is_numeric( $value['y'] ) ) {
+ $value['y'] = $value['y'] + 0;
+ } else {
+ $value['y'] = null;
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'scale', 'Interact_Action_Type_Scale' );
diff --git a/src/action-types/class-action-type-skew.php b/src/action-types/class-action-type-skew.php
index 59be79a..3a965f5 100644
--- a/src/action-types/class-action-type-skew.php
+++ b/src/action-types/class-action-type-skew.php
@@ -46,6 +46,24 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['x'] ) ) {
+ if ( is_numeric( $value['x'] ) ) {
+ $value['x'] = $value['x'] + 0;
+ } else {
+ $value['x'] = null;
+ }
+ }
+ if ( is_array( $value ) && isset( $value['y'] ) ) {
+ if ( is_numeric( $value['y'] ) ) {
+ $value['y'] = $value['y'] + 0;
+ } else {
+ $value['y'] = null;
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'skew', 'Interact_Action_Type_Skew' );
diff --git a/src/action-types/class-action-type-text-color.php b/src/action-types/class-action-type-text-color.php
index d166032..dcb21ef 100644
--- a/src/action-types/class-action-type-text-color.php
+++ b/src/action-types/class-action-type-text-color.php
@@ -34,6 +34,13 @@ public function initialize() {
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['color'] ) ) {
+ $value['color'] = $this->sanitize_style_value( $value['color'] );
+ }
+ return $value;
+ }
}
interact_add_action_type( 'textColor', 'Interact_Action_Type_Text_Color' );
diff --git a/src/action-types/class-action-type-toggle-class.php b/src/action-types/class-action-type-toggle-class.php
index 8b6aa61..018fd7c 100644
--- a/src/action-types/class-action-type-toggle-class.php
+++ b/src/action-types/class-action-type-toggle-class.php
@@ -35,14 +35,14 @@ public function initialize() {
'name' => 'Action',
'type' => 'select',
'default' => 'add',
- 'options' => [
- // Translators: %s is the word 'class'.
- [ 'value' => 'add', 'label' => sprintf( __( 'Add %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
- // Translators: %s is the word 'class'.
- [ 'value' => 'remove', 'label' => sprintf( __( 'Remove %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
- // Translators: %s is the word 'class'.
- [ 'value' => 'toggle', 'label' => sprintf( __( 'Toggle %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
- ]
+ 'options' => [
+ // Translators: %s is the word 'class'.
+ [ 'value' => 'add', 'label' => sprintf( __( 'Add %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
+ // Translators: %s is the word 'class'.
+ [ 'value' => 'remove', 'label' => sprintf( __( 'Remove %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
+ // Translators: %s is the word 'class'.
+ [ 'value' => 'toggle', 'label' => sprintf( __( 'Toggle %s', 'interactions' ), __( 'class', 'interactions' ) ) ],
+ ]
],
];
@@ -50,6 +50,21 @@ public function initialize() {
$this->has_duration = false;
$this->has_easing = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['class'] ) ) {
+ $value['class'] = sanitize_key( $value['class'] );
+ }
+
+ if ( is_array( $value ) && isset( $value['action'] ) ) {
+ $allowed_actions = [ 'add', 'remove', 'toggle' ];
+ if ( ! in_array( $value['action'], $allowed_actions, true ) ) {
+ $value['action'] = 'add';
+ }
+ }
+
+ return $value;
+ }
}
interact_add_action_type( 'toggleClass', 'Interact_Action_Type_Toggle_Class' );
diff --git a/src/action-types/class-action-type-toggle-video.php b/src/action-types/class-action-type-toggle-video.php
index 5f92293..42c864e 100644
--- a/src/action-types/class-action-type-toggle-video.php
+++ b/src/action-types/class-action-type-toggle-video.php
@@ -54,6 +54,23 @@ public function initialize() {
$this->has_duration = false;
$this->has_easing = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['mode'] ) ) {
+ $allowed_modes = [ 'play', 'pause', 'toggle' ];
+ if ( ! in_array( $value['mode'], $allowed_modes, true ) ) {
+ $value['mode'] = 'play';
+ }
+ }
+ if ( is_array( $value ) && isset( $value['startTime'] ) ) {
+ if ( is_numeric( $value['startTime'] ) ) {
+ $value['startTime'] = $value['startTime'] + 0;
+ } else {
+ $value['startTime'] = null;
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'toggleVideo', 'Interact_Action_Type_Toggle_Video' );
diff --git a/src/action-types/class-action-type-update-attribute.php b/src/action-types/class-action-type-update-attribute.php
index 1895fa2..0f15fe9 100644
--- a/src/action-types/class-action-type-update-attribute.php
+++ b/src/action-types/class-action-type-update-attribute.php
@@ -30,6 +30,7 @@ public function initialize() {
'name' => 'Attribute name',
'type' => 'text',
'default' => '',
+ 'restrictedNotice' => __( 'Some attribute names and values are disallowed unless you are an administrator with unfiltered_html capability for security reasons.', 'interactions' ),
],
'value' => [
'name' => 'Value',
@@ -57,6 +58,79 @@ public function initialize() {
$this->has_duration = false;
$this->has_easing = false;
}
+
+ public function is_dangerous_attribute( $attribute_name ) {
+ if ( empty( $attribute_name ) || ! is_string( $attribute_name ) ) {
+ return false;
+ }
+
+ $attribute_name = strtolower( trim( $attribute_name ) );
+
+ // Event handler attributes (onclick, onerror, onload, etc.)
+ if ( preg_match( '/^on[a-z]+/', $attribute_name ) ) {
+ return true;
+ }
+
+ // Attributes that can contain JavaScript URIs or code
+ $dangerous_attributes = [
+ 'href',
+ 'src',
+ 'action',
+ 'formaction',
+ // 'style', // Can contain CSS with expression() or javascript: URIs
+ 'form',
+ 'formmethod',
+ 'formtarget',
+ ];
+
+ return in_array( $attribute_name, $dangerous_attributes, true );
+ }
+
+ public function sanitize_data_for_saving( $value ) {
+ // Sanitize action value: ensure $value is an array and attribute/value are strings.
+ if ( ! is_array( $value ) ) {
+ return new WP_Error(
+ 'invalid_structure',
+ __( 'Value must be an array containing attribute and value keys.', 'interactions' )
+ );
+ }
+
+ // Sanitize attribute name
+ if ( isset( $value['attribute'] ) && is_string( $value['attribute'] ) ) {
+ $value['attribute'] = sanitize_key( $value['attribute'] );
+ }
+
+ // Sanitize value if present, and convert to string.
+ if ( isset( $value['value'] ) ) {
+ $value['value'] = $this->sanitize_style_value( $value['value'] );
+ }
+
+ // Sanitize action field for select option.
+ if ( isset( $value['action'] ) ) {
+ $allowed_actions = [ 'update', 'remove', 'toggle' ];
+ if ( ! in_array( $value['action'], $allowed_actions, true ) ) {
+ $value['action'] = 'update';
+ }
+ }
+
+ if ( current_user_can( 'unfiltered_html' ) ) {
+ return $value;
+ }
+
+ if ( ! empty( $value['attribute'] ) && $this->is_dangerous_attribute( $value['attribute'] ) ) {
+ // Only allow dangerous attributes if user has unfiltered_html capability
+ return new WP_Error(
+ 'invalid_attribute',
+ sprintf(
+ // Translators: %s is the attribute name.
+ __( 'The attribute "%s" requires administrator privileges with unfiltered_html capability to prevent security vulnerabilities.', 'interactions' ),
+ esc_html( $value['attribute'] )
+ )
+ );
+ }
+
+ return $value;
+ }
}
interact_add_action_type( 'updateAttribute', 'Interact_Action_Type_Update_Attribute' );
diff --git a/src/action-types/class-action-type-visibility.php b/src/action-types/class-action-type-visibility.php
index bf551c0..3054db5 100644
--- a/src/action-types/class-action-type-visibility.php
+++ b/src/action-types/class-action-type-visibility.php
@@ -40,6 +40,16 @@ public function initialize() {
$this->has_easing = false;
$this->has_dynamic = false;
}
+
+ public function sanitize_data_for_saving( $value ) {
+ if ( is_array( $value ) && isset( $value['visibility'] ) ) {
+ $allowed_visibilities = [ 'toggle', 'hide', 'show' ];
+ if ( ! in_array( $value['visibility'], $allowed_visibilities, true ) ) {
+ $value['visibility'] = 'toggle';
+ }
+ }
+ return $value;
+ }
}
interact_add_action_type( 'visibility', 'Interact_Action_Type_Visibility' );
diff --git a/src/class-interaction.php b/src/class-interaction.php
index 84f7eb6..fbd9c30 100644
--- a/src/class-interaction.php
+++ b/src/class-interaction.php
@@ -59,13 +59,18 @@ function __construct( $post ) {
* @return int|WP_Error
*/
public static function update( $interaction_data ) {
+ $sanitized_data = self::secure_interaction_data( $interaction_data );
+ if ( is_wp_error( $sanitized_data ) ) {
+ return $sanitized_data;
+ }
+
$post_arr = [
'ID' => self::get_post_id_from_key( $interaction_data['key'] ),
'post_type' => 'interact-interaction',
'post_name' => $interaction_data['key'],
'post_title' => $interaction_data['title'],
// TODO: emojis do not work somehow.
- 'post_content' => wp_slash( maybe_serialize( self::secure_interaction_data( $interaction_data ) ) ),
+ 'post_content' => wp_slash( maybe_serialize( $sanitized_data ) ),
'post_status' => $interaction_data['active'] ? 'publish' : 'interact-inactive',
];
$result = $post_arr['ID'] === 0 ? wp_insert_post( $post_arr ) : wp_update_post( $post_arr );
@@ -78,6 +83,30 @@ public static function update( $interaction_data ) {
return $result;
}
+ /**
+ * Sanitizes the interaction value
+ *
+ * @param mixed $value
+ * @return mixed
+ */
+ public static function sanitize_interaction_value( $value ) {
+ if ( current_user_can( 'unfiltered_html' ) ) {
+ return $value;
+ }
+
+ if ( is_array( $value ) ) {
+ foreach ( $value as $key => $val ) {
+ $value[ $key ] = self::sanitize_interaction_value( $val );
+ }
+ }
+
+ if ( is_string( $value ) ) {
+ $value = wp_kses_post( $value );
+ }
+
+ return $value;
+ }
+
/**
* Runs through all interaction data, and if the action-type has a
* $verify_integrity set to true, it will hash and sign the action's
@@ -92,8 +121,19 @@ public static function secure_interaction_data( $interaction_data ) {
$action_type = $action['type'];
$action_config = interact_get_action_type( $action_type );
- $action_value = $action['value'];
+ // Sanitize the action value for saving.
+ $action_value = self::sanitize_interaction_value( $action['value'] );
+ // Sanitize for specific action type.
+ $action_value = $action_config->sanitize_data_for_saving( $action_value );
+
+ // If the action value is a WP_Error, return the error.
+ if ( is_wp_error( $action_value ) ) {
+ return $action_value;
+ }
+
+ $interaction_data['timelines'][ $timeline_index ]['actions'][ $action_index ]['value'] = $action_value;
+
if ( $action_config->verify_integrity ) {
$signature = hash_hmac( 'sha256', wp_json_encode( $action_value ), interact_salt() );
diff --git a/src/editor/components/timeline/property-control.js b/src/editor/components/timeline/property-control.js
index f142c55..c0b7b29 100644
--- a/src/editor/components/timeline/property-control.js
+++ b/src/editor/components/timeline/property-control.js
@@ -10,6 +10,7 @@ import {
TextControl,
TextareaControl,
ToggleControl,
+ Tooltip,
__experimentalNumberControl as NumberControl,
__experimentalHStack as HStack,
} from '@wordpress/components'
@@ -17,7 +18,7 @@ import { __ } from '@wordpress/i18n'
import { clamp } from 'lodash'
import ColorControl from '../color-control'
import SVGBolt from './images/bolt.svg'
-import { plan } from 'interactions'
+import { plan, currentUserCanUnfilteredHtml } from 'interactions'
const NOOP = () => {}
const EMPTYARR = []
@@ -49,6 +50,29 @@ export const PropertyControl = props => {
) : null
+ // Add a restriction notice.
+ if ( property.restrictedNotice && ! currentUserCanUnfilteredHtml ) {
+ help = (
+ <>
+ { help }{ ' ' }
+