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 }{ ' ' } + { property.restrictedNotice.replace( /<[^>]+>/g, '' ) } } + > + + { __( 'Has restrictions', 'interactions' ) } + + + + ) + } + // Allow the property to override the config for if the option is dynamic. const { hasDynamic: _propertyHasDynamic = true, diff --git a/src/editor/editor.php b/src/editor/editor.php index 68b98a2..a964602 100644 --- a/src/editor/editor.php +++ b/src/editor/editor.php @@ -76,6 +76,7 @@ public function enqueue_editor() { 'restUrl' => trailingslashit( esc_url_raw( rest_url() ) ), // We need to know how to access the REST API. 'restNonce' => wp_create_nonce( 'wp_rest' ), // This needs to be 'wp_rest' to use the built-in nonce verification. 'srcUrl' => untrailingslashit( plugins_url( '/', INTERACT_FILE ) ), + 'currentUserCanUnfilteredHtml' => current_user_can( 'unfiltered_html' ), ) ); wp_localize_script( 'interact-editor', 'interactions', $args ); } From 94f0c195466a4f729ca09e5a77e789cf30d99766 Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Fri, 5 Dec 2025 09:49:24 +0800 Subject: [PATCH 15/21] chore: updated version number and changelog --- interactions.php | 4 ++-- package.json | 2 +- readme.txt | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/interactions.php b/interactions.php index 1a98eb5..5790750 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.1 + * Version: 1.3.2 * * @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.1' ); +defined( 'INTERACT_VERSION' ) || define( 'INTERACT_VERSION', '1.3.2' ); defined( 'INTERACT_FILE' ) || define( 'INTERACT_FILE', __FILE__ ); /** diff --git a/package.json b/package.json index 2410370..6d26698 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interactions", - "version": "1.3.1", + "version": "1.3.2", "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 7b14e02..28b0312 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === Interactions - Create Interactive Experiences in the Block Editor === Contributors: bfintal, gambitph Tags: interaction, interactivity, trigger, blocks, gutenberg -Requires at least: 6.6.4 +Requires at least: 6.7.4 Tested up to: 6.9 Requires PHP: 8.0 -Stable tag: 1.3.1 +Stable tag: 1.3.2 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -149,6 +149,11 @@ The free version includes basic animations and interactions. Premium adds advanc == Changelog == += 1.3.2 = + +* Fixed: Added restrictions for users without unfiltered_html capabilities +* Fixed: Added additional input sanitization + = 1.3.1 = * Fixed: Updated readme info From f3ea079f42acc728ec71163edcaaa6cb95987d6e Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Sat, 6 Dec 2025 09:21:39 +0800 Subject: [PATCH 16/21] fix: add guard before sanitizing --- src/action-types/class-action-type-background-color.php | 4 +++- src/action-types/class-action-type-background-image.php | 4 +++- src/action-types/class-action-type-css-rule.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/action-types/class-action-type-background-color.php b/src/action-types/class-action-type-background-color.php index b905778..ea1225d 100644 --- a/src/action-types/class-action-type-background-color.php +++ b/src/action-types/class-action-type-background-color.php @@ -54,7 +54,9 @@ public function initialize() { // } public function sanitize_data_for_saving( $value ) { - $value['color'] = $this->sanitize_style_value( $value['color'] ); + if ( is_array( $value ) && isset( $value['color'] ) ) { + $value['color'] = $this->sanitize_style_value( $value['color'] ); + } return $value; } } diff --git a/src/action-types/class-action-type-background-image.php b/src/action-types/class-action-type-background-image.php index 0d93b0a..62bda13 100644 --- a/src/action-types/class-action-type-background-image.php +++ b/src/action-types/class-action-type-background-image.php @@ -35,7 +35,9 @@ public function initialize() { } public function sanitize_data_for_saving( $value ) { - $value['image'] = $this->sanitize_style_value( $value['image'] ); + if ( is_array( $value ) && isset( $value['image'] ) ) { + $value['image'] = $this->sanitize_style_value( $value['image'] ); + } return $value; } } diff --git a/src/action-types/class-action-type-css-rule.php b/src/action-types/class-action-type-css-rule.php index 397c899..79822fd 100644 --- a/src/action-types/class-action-type-css-rule.php +++ b/src/action-types/class-action-type-css-rule.php @@ -42,7 +42,9 @@ public function initialize() { } public function sanitize_data_for_saving( $value ) { - $value['value'] = $this->sanitize_style_value( $value['value'] ); + if ( is_array( $value ) && isset( $value['value'] ) ) { + $value['value'] = $this->sanitize_style_value( $value['value'] ); + } return $value; } } From f312c98e28c798faffc35abb136b80e23e6e613a Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Tue, 16 Dec 2025 12:19:54 +0800 Subject: [PATCH 17/21] allow dynamic help to be included --- src/editor/components/timeline/property-control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/components/timeline/property-control.js b/src/editor/components/timeline/property-control.js index c0b7b29..9b63590 100644 --- a/src/editor/components/timeline/property-control.js +++ b/src/editor/components/timeline/property-control.js @@ -172,7 +172,7 @@ export const PropertyControl = props => { const propsToPass = {} if ( property.type === 'id' ) { const idHelp = __( 'Give this data a unique name to make it available to other actions. Can only be lowercase letters, numbers and underscore.', 'interactions' ) - if ( property.help ) { + if ( help ) { help = <>{ help } { idHelp } } else { help = idHelp From f23d9f5dc81b27f2ba3d828da355637082391ad8 Mon Sep 17 00:00:00 2001 From: Benjamin Intal Date: Tue, 16 Dec 2025 12:20:44 +0800 Subject: [PATCH 18/21] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/action-types/class-action-type-rotate.php | 4 +++- src/action-types/class-action-type-toggle-class.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/action-types/class-action-type-rotate.php b/src/action-types/class-action-type-rotate.php index d4dc818..7d914c5 100644 --- a/src/action-types/class-action-type-rotate.php +++ b/src/action-types/class-action-type-rotate.php @@ -91,7 +91,9 @@ public function sanitize_data_for_saving( $value ) { } } - $value['customTransformOrigin'] = $this->sanitize_style_value( $value['customTransformOrigin'] ); + if ( isset( $value['customTransformOrigin'] ) ) { + $value['customTransformOrigin'] = $this->sanitize_style_value( $value['customTransformOrigin'] ); + } return $value; } } diff --git a/src/action-types/class-action-type-toggle-class.php b/src/action-types/class-action-type-toggle-class.php index 018fd7c..873ebf2 100644 --- a/src/action-types/class-action-type-toggle-class.php +++ b/src/action-types/class-action-type-toggle-class.php @@ -53,7 +53,7 @@ public function initialize() { public function sanitize_data_for_saving( $value ) { if ( is_array( $value ) && isset( $value['class'] ) ) { - $value['class'] = sanitize_key( $value['class'] ); + $value['class'] = sanitize_html_class( $value['class'] ); } if ( is_array( $value ) && isset( $value['action'] ) ) { From 0a1aeadb36d96cb35472dbd04e4a42762838f530 Mon Sep 17 00:00:00 2001 From: Alquen Sarmiento Date: Thu, 18 Dec 2025 11:15:13 +0800 Subject: [PATCH 19/21] fix: add checking of html and attribute to Interact_Abstract_Action_Type --- src/action-types/abstract-action-type.php | 118 ++++++++++++++++++ .../class-action-type-update-attribute.php | 27 ---- 2 files changed, 118 insertions(+), 27 deletions(-) diff --git a/src/action-types/abstract-action-type.php b/src/action-types/abstract-action-type.php index 6753a9c..c52efd6 100644 --- a/src/action-types/abstract-action-type.php +++ b/src/action-types/abstract-action-type.php @@ -288,5 +288,123 @@ public function sanitize_style_value( $string ) { return $string; } + + /** + * Detect if an HTML tag is considered dangerous (can execute scripts or + * otherwise modify page behavior). + * + * @param string $tag_name + * @return bool + */ + public function is_dangerous_tag( $tag_name ) { + if ( empty( $tag_name ) || ! is_string( $tag_name ) ) { + return false; + } + + $tag_name = strtolower( trim( $tag_name ) ); + + // Tags that can execute scripts or modify page behavior + $dangerous_tags = [ + 'script', + 'iframe', + 'object', + 'embed', + 'applet', + 'meta', + 'link', + 'style', + 'base', + 'form', + ]; + + return in_array( $tag_name, $dangerous_tags, true ); + } + + /** + * Detect if an HTML attribute is considered dangerous (event handlers, + * attributes that can contain JS URIs, form actions, etc.). + * + * @param string $attribute_name + * @return bool + */ + 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', + 'form', + 'formmethod', + 'formtarget', + ]; + + return in_array( $attribute_name, $dangerous_attributes, true ); + } + + /** + * Validate an HTML snippet for dangerous tags, attributes or protocols. + * Returns true when safe, or a WP_Error describing the violation. + * + * @param string $html + * @return true|WP_Error + */ + public function validate_html_for_saving( $html ) { + if ( ! is_string( $html ) ) { + return new WP_Error( + 'invalid_html', + __( 'HTML must be a string.', 'interactions' ) + ); + } + + // Detect dangerous tags + if ( preg_match_all( '/<\s*([a-z0-9\-]+)/i', $html, $matches ) ) { + foreach ( $matches[1] as $tag ) { + if ( $this->is_dangerous_tag( $tag ) ) { + return new WP_Error( + 'invalid_tag', + sprintf( __( 'The HTML tag "%s" is not allowed.', 'interactions' ), esc_html( $tag ) ) + ); + } + } + } + + // Detect dangerous attributes + if ( preg_match_all( '/<[^>]+>/i', $html, $tagMatches ) ) { + foreach ( $tagMatches[0] as $tagString ) { + if ( preg_match_all( '/([a-zA-Z0-9:\-]+)\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i', $tagString, $attrMatches ) ) { + foreach ( $attrMatches[1] as $attr ) { + if ( $this->is_dangerous_attribute( $attr ) ) { + return new WP_Error( + 'invalid_attribute', + sprintf( __( 'The HTML attribute "%s" is not allowed.', 'interactions' ), esc_html( $attr ) ) + ); + } + } + } + } + } + + // Detect disallowed protocols + if ( preg_match( '/javascript:\s*/i', $html ) || preg_match( '/data:\s*text\//i', $html ) ) { + return new WP_Error( + 'invalid_protocol', + __( 'The HTML contains disallowed protocols (javascript: or data:).', 'interactions' ) + ); + } + + return true; + } } } diff --git a/src/action-types/class-action-type-update-attribute.php b/src/action-types/class-action-type-update-attribute.php index 0f15fe9..4c342da 100644 --- a/src/action-types/class-action-type-update-attribute.php +++ b/src/action-types/class-action-type-update-attribute.php @@ -59,33 +59,6 @@ public function initialize() { $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 ) ) { From 7b8dcee2d1c045d77dd2f99154f71812b679a255 Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Thu, 8 Jan 2026 12:16:18 +0800 Subject: [PATCH 20/21] test: updated build workflow --- .github/workflows/plugin-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/plugin-build.yml b/.github/workflows/plugin-build.yml index f426b74..e7d9674 100644 --- a/.github/workflows/plugin-build.yml +++ b/.github/workflows/plugin-build.yml @@ -2,9 +2,9 @@ name: Plugin Build on: push: - branches: [ master, main ] + branches: [ master, develop ] pull_request: - branches: [ master, main ] + branches: [ master, develop ] jobs: build: From dc7d7f8e50d1abe44f0bf53b6b1cc1fd87d43c35 Mon Sep 17 00:00:00 2001 From: "bfintal@gmail.com" <> Date: Thu, 8 Jan 2026 13:02:42 +0800 Subject: [PATCH 21/21] added suffix to version --- scripts/package.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/scripts/package.js b/scripts/package.js index 473457e..a1e5c95 100644 --- a/scripts/package.js +++ b/scripts/package.js @@ -292,6 +292,34 @@ function cleanupEmptyDirectories( dir ) { } } +function updatePluginHeaderVersion( buildDir, suffix ) { + if ( ! suffix ) { + return + } + + const pluginFileName = IS_PREMIUM_BUILD ? 'plugin.php' : 'interactions.php' + const pluginFilePath = path.join( buildDir, pluginFileName ) + + if ( ! fs.existsSync( pluginFilePath ) ) { + return + } + + let content = fs.readFileSync( pluginFilePath, 'utf8' ) + // Append folder suffix to version in plugin header + content = content.replace( + /^(\s*\*\s*Version:\s*)([^\r\n]+)/m, + ( match, prefix, version ) => { + // Only append if suffix is not already present + if ( ! version.includes( suffix ) ) { + return prefix + version + '-' + suffix + } + return match + } + ) + fs.writeFileSync( pluginFilePath, content ) + console.log( `πŸ“ Updated version in ${ pluginFileName } to include suffix: ${ suffix }` ) +} + async function packagePlugin() { console.log( 'πŸš€ Starting plugin packaging...' ) console.log( `πŸ“¦ Build type: ${ IS_PREMIUM_BUILD ? 'Premium' : 'Free' }` ) @@ -332,6 +360,9 @@ async function packagePlugin() { console.log( 'πŸ”’ Adding security index.php files...' ) addSecurityFiles( BUILD_DIR ) + console.log( 'πŸ“ Updating plugin header version...' ) + updatePluginHeaderVersion( BUILD_DIR, folderSuffix ) + console.log( '🧹 Cleaning up empty directories...' ) cleanupEmptyDirectories( BUILD_DIR )