Skip to content

Commit 7dcfa30

Browse files
Arukuenbfintalcoderabbitai[bot]
authored
fix: add guard before sanitizing (#9)
* fix: downgrading and upgrading multiple times should migrate * chore: updated readme * chore: dont' exclude pro in search * build: added file renaming for premium build * chore: updated readme screenshots * chore: updated plugin name to be more descriptive * chore: updated description * fix: conflict with others that use freemius activation * chore: updated readme * chore: updated readme * chore: version bumped to 1.3.1 * chore: updated plugin name and readme info * chore: updated tested up to * Added sanitization and security for users without unfiltered_html capability * chore: updated version number and changelog * fix: add guard before sanitizing * allow dynamic help to be included * Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * fix: add checking of html and attribute to Interact_Abstract_Action_Type * test: updated build workflow * added suffix to version --------- Co-authored-by: bfintal@gmail.com <> Co-authored-by: Benjamin Intal <bfintal@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 7f199ce commit 7dcfa30

26 files changed

Lines changed: 608 additions & 84 deletions

.github/workflows/plugin-build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Plugin Build
22

33
on:
44
push:
5-
branches: [ master, main ]
5+
branches: [ master, develop ]
66
pull_request:
7-
branches: [ master, main ]
7+
branches: [ master, develop ]
88

99
jobs:
1010
build:

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,8 @@
4040
"editor.codeActionsOnSave": {
4141
"source.fixAll.stylelint": "never"
4242
}
43+
},
44+
"search.exclude": {
45+
"**/pro__premium_only": false
4346
}
4447
}

interactions.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Author URI: http://gambit.ph
88
* License: GPLv2 or later
99
* Text Domain: interactions
10-
* Version: 1.3.0
10+
* Version: 1.3.2
1111
*
1212
* @fs_premium_only /freemius.php, /freemius/
1313
*/
@@ -18,7 +18,7 @@
1818
}
1919

2020
defined( 'INTERACT_BUILD' ) || define( 'INTERACT_BUILD', 'free' );
21-
defined( 'INTERACT_VERSION' ) || define( 'INTERACT_VERSION', '1.3.0' );
21+
defined( 'INTERACT_VERSION' ) || define( 'INTERACT_VERSION', '1.3.2' );
2222
defined( 'INTERACT_FILE' ) || define( 'INTERACT_FILE', __FILE__ );
2323

2424
/**
@@ -31,8 +31,8 @@ function interact_on_activation() {
3131
// Run migration if version not set or outdated
3232
if ( ! $saved_version || version_compare( $saved_version, INTERACT_VERSION, '<' ) ) {
3333
do_action( 'interact/on_plugin_update', $saved_version, INTERACT_VERSION );
34-
update_option( 'interact_plugin_version', INTERACT_VERSION );
3534
}
35+
update_option( 'interact_plugin_version', INTERACT_VERSION );
3636
}
3737
}
3838
register_activation_hook( __FILE__, 'interact_on_activation' );

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "interactions",
3-
"version": "1.3.0",
3+
"version": "1.3.2",
44
"description": "Make your blocks interactive! Effortlessly set triggers that do actions",
55
"author": "Benjamin Intal of Gambit",
66
"private": true,

readme.txt

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
1-
=== Interactions ===
1+
=== Interactions - Create Interactive Experiences in the Block Editor ===
22
Contributors: bfintal, gambitph
33
Tags: interaction, interactivity, trigger, blocks, gutenberg
4-
Requires at least: 6.6.4
5-
Tested up to: 6.8.3
4+
Requires at least: 6.7.4
5+
Tested up to: 6.9
66
Requires PHP: 8.0
7-
Stable tag: 1.3.0
7+
Stable tag: 1.3.2
88
License: GPLv2 or later
99
License URI: https://www.gnu.org/licenses/gpl-2.0.html
1010

1111
Add animations and interactivity to your blocks. Choose from ready-made effects like scroll & hover in the Interactions Library, or build your own.
1212

1313
== Description ==
1414

15-
**Interactions – WordPress Animations, Effects & Functionality for Gutenberg Blocks**
15+
**Interactions – WordPress Animations, Interactive Experiences for Gutenberg Blocks**
1616

17-
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.
17+
[Visit our website](https://wpinteractions.com) to learn more about how Interactions work.
18+
19+
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.
1820

1921
You don't need coding skills or complex tools. With Interactions, you can:
2022

21-
- **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.
22-
- **Build your own custom effects** – Use a simple **Trigger → Action** system. Example: "On scroll → Fade in block", or "On click → Play video".
23-
- **Add functional features** – Securely update post data, handle form submissions, display user info, copy text to clipboard, and more without coding.
23+
- **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)
24+
- **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)
25+
- **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)
2426

25-
Whether you want subtle hover effects, attention-grabbing story-telling animations, playful micro-interactions, or powerful functional features, Interactions makes it possible.
27+
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.
2628

2729
### 🚀 Features
2830

29-
Create custom interactions easily with a simple Trigger → Action builder. Features include:
30-
31+
Create [custom interactions](https://docs.wpinteractions.com/article/571-what-are-interactions) easily with a simple Trigger → Action builder. Features include:
3132

3233
**Animations & Visual Effects:**
3334

@@ -70,6 +71,8 @@ Create custom interactions easily with a simple Trigger → Action builder. Feat
7071

7172
### 💎 What's in Premium?
7273

74+
[Check our pricing page](https://wpinteractions.com/pricing/) to learn more about what's in Interactions premium.
75+
7376
**Advanced Interactions:**
7477

7578
- **Scroll Strength** – Measure scroll intensity
@@ -98,6 +101,11 @@ Create custom interactions easily with a simple Trigger → Action builder. Feat
98101
- **Regular Updates** – New features and improvements
99102
- **Commercial License** – Use in client projects
100103

104+
**Source Code:**
105+
106+
The source code for this plugin is available on GitHub:
107+
https://github.com/gambitph/Interactions
108+
101109
== Installation ==
102110

103111
1. Install “Interactions” from the WordPress Plugin Directory, or upload it to `/wp-content/plugins/interactions/`.
@@ -133,23 +141,32 @@ The free version includes basic animations and interactions. Premium adds advanc
133141

134142
== Screenshots ==
135143

136-
1. Interaction Library – Pre-built animations and effects.
144+
1. Adding from the Interaction Library – Pre-built animations and effects.
137145
2. Advanced trigger and action timeline builder – Create custom interactions with flexible logic and multiple steps.
146+
3. Interaction Library contents – Pre-built animations and effects.
138147

139-
== Source ==
140-
141-
The source code for this plugin is available on GitHub:
142-
https://github.com/gambitph/Interactions
148+
== Upgrade Notice ==
143149

144150
== Changelog ==
145151

152+
= 1.3.2 =
153+
154+
* Fixed: Added restrictions for users without unfiltered_html capabilities
155+
* Fixed: Added additional input sanitization
156+
157+
= 1.3.1 =
158+
159+
* Fixed: Updated readme info
160+
* Fixed: Updated long name in the plugins page
161+
* Fixed: License activation issue
162+
146163
= 1.3.0 =
147164

148165
* New: Interaction library
149-
* New: Initial release in the WordPress Plugin Directory!
150166
* New: Block name field is now searchable #70
151167
* New: Import / export functionality #71
152168
* New: Box shadow action #81
169+
* New: 3D Rotate - new transform origin option
153170
* Fixed: Hover interaction glitches when hovering too fast #9
154171
* Fixed: On enter viewport doesn't always trigger when on mobile #23
155172
* Fixed: Confetti action - selecting window will no longer show a display target warning message #74

scripts/package.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,34 @@ function cleanupEmptyDirectories( dir ) {
292292
}
293293
}
294294

295+
function updatePluginHeaderVersion( buildDir, suffix ) {
296+
if ( ! suffix ) {
297+
return
298+
}
299+
300+
const pluginFileName = IS_PREMIUM_BUILD ? 'plugin.php' : 'interactions.php'
301+
const pluginFilePath = path.join( buildDir, pluginFileName )
302+
303+
if ( ! fs.existsSync( pluginFilePath ) ) {
304+
return
305+
}
306+
307+
let content = fs.readFileSync( pluginFilePath, 'utf8' )
308+
// Append folder suffix to version in plugin header
309+
content = content.replace(
310+
/^(\s*\*\s*Version:\s*)([^\r\n]+)/m,
311+
( match, prefix, version ) => {
312+
// Only append if suffix is not already present
313+
if ( ! version.includes( suffix ) ) {
314+
return prefix + version + '-' + suffix
315+
}
316+
return match
317+
}
318+
)
319+
fs.writeFileSync( pluginFilePath, content )
320+
console.log( `📝 Updated version in ${ pluginFileName } to include suffix: ${ suffix }` )
321+
}
322+
295323
async function packagePlugin() {
296324
console.log( '🚀 Starting plugin packaging...' )
297325
console.log( `📦 Build type: ${ IS_PREMIUM_BUILD ? 'Premium' : 'Free' }` )
@@ -312,6 +340,16 @@ async function packagePlugin() {
312340
}
313341
}
314342

343+
// Rename interactions.php to plugin.php for premium builds only
344+
if ( IS_PREMIUM_BUILD ) {
345+
const oldPath = path.join( BUILD_DIR, 'interactions.php' )
346+
const newPath = path.join( BUILD_DIR, 'plugin.php' )
347+
if ( fs.existsSync( oldPath ) ) {
348+
fs.renameSync( oldPath, newPath )
349+
console.log( '📝 Renamed interactions.php to plugin.php for premium build' )
350+
}
351+
}
352+
315353
console.log( '📁 Copying source directories...' )
316354
// Pass isSrcRoot = true for the top-level src folder
317355
copyDir( 'src', path.join( BUILD_DIR, 'src' ), true )
@@ -322,6 +360,9 @@ async function packagePlugin() {
322360
console.log( '🔒 Adding security index.php files...' )
323361
addSecurityFiles( BUILD_DIR )
324362

363+
console.log( '📝 Updating plugin header version...' )
364+
updatePluginHeaderVersion( BUILD_DIR, folderSuffix )
365+
325366
console.log( '🧹 Cleaning up empty directories...' )
326367
cleanupEmptyDirectories( BUILD_DIR )
327368

src/action-types/abstract-action-type.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,5 +256,155 @@ public function initilize_action( $action, $animation_data ) {
256256

257257
return $action;
258258
}
259+
260+
/**
261+
* Sanitizes the action's value before saving.
262+
*
263+
* Override this in a child class to implement specific sanitization.
264+
*
265+
* @param mixed $value The action value to sanitize.
266+
* @return mixed The sanitized action value.
267+
*/
268+
public function sanitize_data_for_saving( $value ) {
269+
// By default, no sanitization is applied.
270+
return $value;
271+
}
272+
273+
/**
274+
* Remove any `expression(...)` and `javascript:` content from a CSS style string for security.
275+
*
276+
* @param string $string
277+
* @return string
278+
*/
279+
public function sanitize_style_value( $string ) {
280+
if ( ! is_string( $string ) ) {
281+
return $string;
282+
}
283+
// Remove all expression(...) (case-insensitive).
284+
$string = preg_replace( '/expression\s*\((?:[^\(\)]|(?R))*\)/i', '', $string );
285+
286+
// Remove all javascript: URIs (case-insensitive).
287+
$string = preg_replace( '/javascript\s*:/i', '', $string );
288+
289+
return $string;
290+
}
291+
292+
/**
293+
* Detect if an HTML tag is considered dangerous (can execute scripts or
294+
* otherwise modify page behavior).
295+
*
296+
* @param string $tag_name
297+
* @return bool
298+
*/
299+
public function is_dangerous_tag( $tag_name ) {
300+
if ( empty( $tag_name ) || ! is_string( $tag_name ) ) {
301+
return false;
302+
}
303+
304+
$tag_name = strtolower( trim( $tag_name ) );
305+
306+
// Tags that can execute scripts or modify page behavior
307+
$dangerous_tags = [
308+
'script',
309+
'iframe',
310+
'object',
311+
'embed',
312+
'applet',
313+
'meta',
314+
'link',
315+
'style',
316+
'base',
317+
'form',
318+
];
319+
320+
return in_array( $tag_name, $dangerous_tags, true );
321+
}
322+
323+
/**
324+
* Detect if an HTML attribute is considered dangerous (event handlers,
325+
* attributes that can contain JS URIs, form actions, etc.).
326+
*
327+
* @param string $attribute_name
328+
* @return bool
329+
*/
330+
public function is_dangerous_attribute( $attribute_name ) {
331+
if ( empty( $attribute_name ) || ! is_string( $attribute_name ) ) {
332+
return false;
333+
}
334+
335+
$attribute_name = strtolower( trim( $attribute_name ) );
336+
337+
// Event handler attributes (onclick, onerror, onload, etc.)
338+
if ( preg_match( '/^on[a-z]+/', $attribute_name ) ) {
339+
return true;
340+
}
341+
342+
// Attributes that can contain JavaScript URIs or code
343+
$dangerous_attributes = [
344+
'href',
345+
'src',
346+
'action',
347+
'formaction',
348+
'form',
349+
'formmethod',
350+
'formtarget',
351+
];
352+
353+
return in_array( $attribute_name, $dangerous_attributes, true );
354+
}
355+
356+
/**
357+
* Validate an HTML snippet for dangerous tags, attributes or protocols.
358+
* Returns true when safe, or a WP_Error describing the violation.
359+
*
360+
* @param string $html
361+
* @return true|WP_Error
362+
*/
363+
public function validate_html_for_saving( $html ) {
364+
if ( ! is_string( $html ) ) {
365+
return new WP_Error(
366+
'invalid_html',
367+
__( 'HTML must be a string.', 'interactions' )
368+
);
369+
}
370+
371+
// Detect dangerous tags
372+
if ( preg_match_all( '/<\s*([a-z0-9\-]+)/i', $html, $matches ) ) {
373+
foreach ( $matches[1] as $tag ) {
374+
if ( $this->is_dangerous_tag( $tag ) ) {
375+
return new WP_Error(
376+
'invalid_tag',
377+
sprintf( __( 'The HTML tag "%s" is not allowed.', 'interactions' ), esc_html( $tag ) )
378+
);
379+
}
380+
}
381+
}
382+
383+
// Detect dangerous attributes
384+
if ( preg_match_all( '/<[^>]+>/i', $html, $tagMatches ) ) {
385+
foreach ( $tagMatches[0] as $tagString ) {
386+
if ( preg_match_all( '/([a-zA-Z0-9:\-]+)\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i', $tagString, $attrMatches ) ) {
387+
foreach ( $attrMatches[1] as $attr ) {
388+
if ( $this->is_dangerous_attribute( $attr ) ) {
389+
return new WP_Error(
390+
'invalid_attribute',
391+
sprintf( __( 'The HTML attribute "%s" is not allowed.', 'interactions' ), esc_html( $attr ) )
392+
);
393+
}
394+
}
395+
}
396+
}
397+
}
398+
399+
// Detect disallowed protocols
400+
if ( preg_match( '/javascript:\s*/i', $html ) || preg_match( '/data:\s*text\//i', $html ) ) {
401+
return new WP_Error(
402+
'invalid_protocol',
403+
__( 'The HTML contains disallowed protocols (javascript: or data:).', 'interactions' )
404+
);
405+
}
406+
407+
return true;
408+
}
259409
}
260410
}

src/action-types/class-action-type-background-color.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public function initialize() {
5252

5353
// return parent::initilize_action( $action, $animation_data );
5454
// }
55+
56+
public function sanitize_data_for_saving( $value ) {
57+
if ( is_array( $value ) && isset( $value['color'] ) ) {
58+
$value['color'] = $this->sanitize_style_value( $value['color'] );
59+
}
60+
return $value;
61+
}
5562
}
5663

5764
interact_add_action_type( 'backgroundColor', 'Interact_Action_Type_Background_Color' );

0 commit comments

Comments
 (0)