Skip to content

Conversation

@bfintal
Copy link
Contributor

@bfintal bfintal commented Dec 5, 2025

Summary by CodeRabbit

  • New Features

    • Added security restriction notices for users without administrative permissions.
  • Bug Fixes

    • Enhanced data validation and sanitization across action types.
    • Improved attribute safety checks for security.
  • Documentation

    • Updated plugin description, compatibility requirements, and comprehensive changelog.
  • Chores

    • Bumped version to 1.3.2.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

Version bumped to 1.3.2 across configuration files. Comprehensive sanitization framework added to action type classes with type-specific data validation. Security enhancements include permission-based attribute restrictions and dangerous attribute detection. Editor UI updated to surface permission restrictions. Build script modified for premium version handling. Documentation expanded significantly.

Changes

Cohort / File(s) Change Summary
Version and Configuration Updates
.vscode/settings.json, interactions.php, package.json
Version bumped from 1.3.0 to 1.3.2; added search exclusion pattern for premium-only files; updated plugin header and constants.
Documentation
readme.txt
Comprehensive updates including refined plugin title, adjusted version requirements (6.6.4 → 6.7.4, 6.8.3 → 6.9), expanded feature descriptions with resource links, enhanced FAQ, improved screenshot captions, and added upgrade notice section.
Build Script
scripts/package.js
Added conditional rename logic: when building premium version, renames build directory interactions.php to plugin.php if it exists.
Core Sanitization Framework
src/action-types/abstract-action-type.php
Added two public methods: sanitize_data_for_saving() hook for per-action sanitization and sanitize_style_value() for CSS security (blocks expression(...) and javascript: patterns).
Action Type Sanitization
src/action-types/class-action-type-background-color.php, class-action-type-background-image.php, class-action-type-css-rule.php, class-action-type-display.php, class-action-type-move.php, class-action-type-opacity.php, class-action-type-rotate.php, class-action-type-scale.php, class-action-type-skew.php, class-action-type-text-color.php, class-action-type-toggle-class.php, class-action-type-toggle-video.php, class-action-type-visibility.php
Each action type class enhanced with sanitize_data_for_saving() method implementing type-specific validation (numeric coercion, whitelist validation, CSS sanitization, etc.).
Attribute Action Security
src/action-types/class-action-type-update-attribute.php
Added is_dangerous_attribute() to identify restricted attributes (event handlers, href, src, etc.); sanitize_data_for_saving() enforces permission checks, blocks unauthorized dangerous attribute usage, and returns WP_Error on violations; added restrictedNotice label to attribute property.
Interaction Class Sanitization
src/class-interaction.php
Enhanced update workflow to apply secure_interaction_data() sanitization before persistence; added sanitize_interaction_value() public static method for recursive value sanitization; added cache invalidation post-update.
Editor UI Enhancements
src/editor/components/timeline/property-control.js, src/editor/editor.php
Added restriction notice UI with Tooltip when property has restrictions and user lacks unfiltered_html capability; exposed currentUserCanUnfilteredHtml to frontend via localized script.
Freemius License Dialog
src/freemius.php
Refactored license key modal: replaced inline onClick handlers with encapsulated IIFE event listeners; improved "Activate"/"Deactivate" button logic with license validation, busy state management, and AJAX request handling.

Sequence Diagram

sequenceDiagram
    participant User
    participant Editor as Editor UI
    participant Interaction as Interaction Class
    participant ActionType as Action Type Handler
    participant Security as Permission Check
    participant Database as Post Storage
    
    User->>Editor: Submit action with data
    Editor->>Interaction: Call update with action data
    Interaction->>Interaction: Call secure_interaction_data()
    Interaction->>ActionType: Call sanitize_data_for_saving()
    
    alt Dangerous Attribute Detected
        ActionType->>Security: Check if user has unfiltered_html
        alt User Lacks Permission
            Security-->>ActionType: Return WP_Error
            ActionType-->>Interaction: Return WP_Error
            Interaction-->>Editor: Display error
        else User Has Permission
            Security-->>ActionType: Proceed
            ActionType->>ActionType: Apply sanitization
            ActionType-->>Interaction: Return sanitized value
        end
    else Safe Action Type
        ActionType->>ActionType: Apply type-specific sanitization
        ActionType-->>Interaction: Return sanitized value
    end
    
    Interaction->>Interaction: Validate sanitized data
    Interaction->>Database: Save sanitized action
    Interaction->>Interaction: Clear cache
    Interaction-->>Editor: Return success
    Editor-->>User: Display confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

Areas requiring extra attention:

  • Security-critical changes in src/action-types/class-action-type-update-attribute.php: Verify dangerous attribute list is comprehensive and permission bypass logic is sound
  • Sanitization pattern consistency across 13 action type files: Confirm type-specific validation rules align with requirements and don't introduce regressions
  • Interaction class update workflow (src/class-interaction.php): Trace sanitization flow to ensure errors are properly handled and cached data remains consistent
  • Freemius event handling refactoring (src/freemius.php): Validate AJAX request flow, error handling, and page reload behavior
  • Permission exposure in frontend (src/editor/editor.php): Ensure capability check is correctly localized and UI properly responds to permission state

Poem

🐰 Hops with glee, sanitizing the code,
With type-safe checks on every road,
Dangerous attributes now must prove their way,
Permissions enforced—security's here to stay! 🛡️
One-point-three-point-two, cleaner than before,
More robust than ever, we'll hop no more!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix: security for unfiltered html cap' directly addresses the main objective of this PR, which implements security sanitization for unfiltered_html capability throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/security

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bfintal bfintal self-assigned this Dec 5, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (1)
src/action-types/class-action-type-scale.php (1)

50-66: LGTM! Follows consistent pattern.

The implementation is correct and consistent with other numeric field validations (e.g., skew, rotate).

🧹 Nitpick comments (6)
readme.txt (1)

1-8: Docs and metadata match the release; consider filling the Upgrade Notice

The updated header (requires/tested/stable), expanded description, feature lists, and the 1.3.2 changelog entries (“restrictions for users without unfiltered_html capabilities” and “additional input sanitization”) all line up well with the code changes in this PR.

The only minor nit is that == Upgrade Notice == is present but empty; if you want that section to show something in the plugin directory, consider adding a brief note for 1.3.2, otherwise you could also omit the section entirely.

Also applies to: 15-28, 72-75, 104-108, 144-148, 152-156, 159-161, 169-170

src/action-types/class-action-type-skew.php (1)

50-66: LGTM! Consider extracting numeric validation helper.

The sanitization logic is correct and includes proper null checks. The numeric validation pattern (lines 52-56 and 59-63) is identical across multiple action types (scale, rotate, move, opacity).

Consider extracting this pattern to a helper method in the abstract base class:

protected function sanitize_numeric_field( $value, $key ) {
    if ( is_array( $value ) && isset( $value[ $key ] ) ) {
        if ( is_numeric( $value[ $key ] ) ) {
            $value[ $key ] = $value[ $key ] + 0;
        } else {
            $value[ $key ] = null;
        }
    }
    return $value;
}

Then use it as: $value = $this->sanitize_numeric_field( $value, 'x' );

src/action-types/class-action-type-toggle-class.php (1)

54-57: sanitize_key() may be too restrictive for CSS class names.

CSS class names are case-sensitive and can contain uppercase letters (e.g., myButton, Card__header). sanitize_key() converts to lowercase and removes characters beyond [a-z0-9_-], which may break BEM-style or component-based class naming conventions.

Consider using a more permissive sanitization that preserves case:

 if ( is_array( $value ) && isset( $value['class'] ) ) {
-    $value['class'] = sanitize_key( $value['class'] );
+    // Sanitize class name: allow alphanumeric, hyphens, underscores, preserve case
+    $value['class'] = preg_replace( '/[^a-zA-Z0-9_\-]/', '', $value['class'] );
 }
src/action-types/class-action-type-move.php (1)

62-75: LGTM! Clean loop-based sanitization.

The loop approach is more maintainable than repetitive conditionals used in sibling files (e.g., class-action-type-scale.php, class-action-type-skew.php). The intentional preservation of empty strings aligns with the property defaults.

For consistency with other action types that check is_array($value) first, you could optionally add that guard:

 public function sanitize_data_for_saving( $value ) {
+    if ( ! is_array( $value ) ) {
+        return $value;
+    }
     // Ensure x, y, z are sanitized as numeric...
     foreach ( [ 'x', 'y', 'z' ] as $key ) {
src/action-types/abstract-action-type.php (1)

279-290: Good foundational CSS sanitization, consider extending coverage.

The implementation correctly handles the main CSS injection vectors (expression() and javascript: URIs). The recursive regex for nested parentheses is well-crafted.

For comprehensive protection, consider also sanitizing:

  • url() values containing javascript: or data: URIs
  • Legacy IE vectors like -moz-binding and behavior:
 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 );

+    // Remove data: URIs which can execute scripts.
+    $string = preg_replace( '/data\s*:/i', '', $string );
+
+    // Remove legacy IE behavior/binding vectors.
+    $string = preg_replace( '/(behavior|(-moz-)?binding)\s*:/i', '', $string );

     return $string;
 }
src/action-types/class-action-type-update-attribute.php (1)

62-87: Consider adding srcdoc to the dangerous attributes list.

The srcdoc attribute (used with iframes) can contain arbitrary HTML and JavaScript, making it as dangerous as href with javascript: URIs. It should be included in the dangerous attributes list.

Additionally, you might consider adding poster and data (object tag) for completeness, though they are lower risk.

 $dangerous_attributes = [
     'href',
     'src',
+    'srcdoc',
     'action',
     'formaction',
     // 'style', // Can contain CSS with expression() or javascript: URIs
     'form',
     'formmethod',
     'formtarget',
+    'poster',
+    'data',
 ];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f199ce and 94f0c19.

📒 Files selected for processing (25)
  • .vscode/settings.json (1 hunks)
  • interactions.php (3 hunks)
  • package.json (1 hunks)
  • readme.txt (4 hunks)
  • scripts/package.js (1 hunks)
  • src/action-types/abstract-action-type.php (1 hunks)
  • src/action-types/class-action-type-background-color.php (1 hunks)
  • src/action-types/class-action-type-background-image.php (1 hunks)
  • src/action-types/class-action-type-css-rule.php (1 hunks)
  • src/action-types/class-action-type-display.php (1 hunks)
  • src/action-types/class-action-type-move.php (1 hunks)
  • src/action-types/class-action-type-opacity.php (1 hunks)
  • src/action-types/class-action-type-redirect-to-url.php (1 hunks)
  • src/action-types/class-action-type-rotate.php (1 hunks)
  • src/action-types/class-action-type-scale.php (1 hunks)
  • src/action-types/class-action-type-skew.php (1 hunks)
  • src/action-types/class-action-type-text-color.php (1 hunks)
  • src/action-types/class-action-type-toggle-class.php (1 hunks)
  • src/action-types/class-action-type-toggle-video.php (1 hunks)
  • src/action-types/class-action-type-update-attribute.php (2 hunks)
  • src/action-types/class-action-type-visibility.php (1 hunks)
  • src/class-interaction.php (3 hunks)
  • src/editor/components/timeline/property-control.js (2 hunks)
  • src/editor/editor.php (1 hunks)
  • src/freemius.php (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
src/action-types/class-action-type-text-color.php (3)
src/action-types/abstract-action-type.php (2)
  • sanitize_data_for_saving (268-271)
  • sanitize_style_value (279-290)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-rotate.php (8)
src/action-types/abstract-action-type.php (2)
  • sanitize_data_for_saving (268-271)
  • sanitize_style_value (279-290)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-visibility.php (10)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/abstract-action-type.php (15)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-move.php (1)
  • sanitize_data_for_saving (62-75)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-rotate.php (1)
  • sanitize_data_for_saving (67-96)
src/action-types/class-action-type-scale.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-skew.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-css-rule.php (11)
src/action-types/abstract-action-type.php (2)
  • sanitize_data_for_saving (268-271)
  • sanitize_style_value (279-290)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-scale.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-skew.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/class-interaction.php (16)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-move.php (1)
  • sanitize_data_for_saving (62-75)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-rotate.php (1)
  • sanitize_data_for_saving (67-96)
src/action-types/class-action-type-scale.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-skew.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-toggle-class.php (4)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-background-image.php (3)
src/action-types/abstract-action-type.php (2)
  • sanitize_data_for_saving (268-271)
  • sanitize_style_value (279-290)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-redirect-to-url.php (8)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-toggle-video.php (15)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-move.php (1)
  • sanitize_data_for_saving (62-75)
src/action-types/class-action-type-opacity.php (1)
  • sanitize_data_for_saving (40-49)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-rotate.php (1)
  • sanitize_data_for_saving (67-96)
src/action-types/class-action-type-scale.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-skew.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
src/action-types/class-action-type-update-attribute.php (3)
src/action-types/abstract-action-type.php (2)
  • sanitize_data_for_saving (268-271)
  • sanitize_style_value (279-290)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/class-action-type-opacity.php (15)
src/action-types/abstract-action-type.php (1)
  • sanitize_data_for_saving (268-271)
src/action-types/class-action-type-background-color.php (1)
  • sanitize_data_for_saving (56-59)
src/action-types/class-action-type-background-image.php (1)
  • sanitize_data_for_saving (37-40)
src/action-types/class-action-type-css-rule.php (1)
  • sanitize_data_for_saving (44-47)
src/action-types/class-action-type-display.php (1)
  • sanitize_data_for_saving (59-80)
src/action-types/class-action-type-move.php (1)
  • sanitize_data_for_saving (62-75)
src/action-types/class-action-type-redirect-to-url.php (1)
  • sanitize_data_for_saving (41-50)
src/action-types/class-action-type-rotate.php (1)
  • sanitize_data_for_saving (67-96)
src/action-types/class-action-type-scale.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-skew.php (1)
  • sanitize_data_for_saving (50-66)
src/action-types/class-action-type-text-color.php (1)
  • sanitize_data_for_saving (38-43)
src/action-types/class-action-type-toggle-class.php (1)
  • sanitize_data_for_saving (54-67)
src/action-types/class-action-type-toggle-video.php (1)
  • sanitize_data_for_saving (58-73)
src/action-types/class-action-type-update-attribute.php (1)
  • sanitize_data_for_saving (89-133)
src/action-types/class-action-type-visibility.php (1)
  • sanitize_data_for_saving (44-52)
🔇 Additional comments (20)
src/freemius.php (2)

604-604: LGTM!

Good refactor removing the inline onClick handler in favor of a class-based approach with an event listener. This is more maintainable and aligns with the encapsulated event handling pattern in the IIFE below.


613-622: LGTM!

Good encapsulation using an IIFE. The click-outside-to-close handler properly checks the busy state to prevent accidental dismissal during async operations. Event handlers are well-scoped.

package.json (1)

3-3: Version bump consistency looks good

Version updated to 1.3.2 here matches the plugin header and readme stable tag, keeping build tooling and PHP code in sync.

.vscode/settings.json (1)

43-46: Explicitly including pro__premium_only in search is reasonable

Setting "**/pro__premium_only": false under search.exclude ensures that folder stays searchable even if excluded elsewhere (e.g., user/workspace defaults). If that’s the intention (to always see premium code in search), this is a good, low-impact editor tweak.

interactions.php (1)

10-10: Versioning and activation option update are consistent

Updating the header Version, INTERACT_VERSION constant, and calling update_option( 'interact_plugin_version', INTERACT_VERSION ) inside interact_on_activation() keeps the stored version aligned with the running code and maintains the existing migration flow via interact/on_plugin_update. No issues from what’s visible here.

Also applies to: 21-21, 35-36

src/editor/editor.php (1)

66-80: Using currentUserCanUnfilteredHtml in localized data is appropriate

Adding currentUserCanUnfilteredHtml => current_user_can( 'unfiltered_html' ) cleanly exposes this capability to the editor script so the UI can show restrictions for non‑privileged users. This is safe to expose and will serialize as a proper boolean.

Verified that the flag is used exclusively for UI gating—specifically to display a restrictedNotice in the property control component when users lack the capability. Server-side enforcement is robustly implemented: unprivileged users trigger wp_kses_post() sanitization on string values, and dangerous HTML attributes are blocked via validation logic that returns WP_Error for violations. The client flag drives UX only; security decisions remain fully enforced on the server.

src/action-types/class-action-type-display.php (1)

59-80: LGTM! Proper whitelist validation.

The sanitization logic correctly validates the display value against allowed CSS display values and provides a safe fallback. The strict comparison in in_array() is appropriate for security.

src/action-types/class-action-type-toggle-video.php (1)

58-73: LGTM! Comprehensive validation.

The sanitization correctly validates both the mode enum and numeric startTime field with appropriate fallbacks and null safety.

src/action-types/class-action-type-redirect-to-url.php (1)

41-50: LGTM! Appropriate URL sanitization.

Using esc_url() is the correct approach for sanitizing URLs in WordPress, with proper type checking and null fallback.

src/action-types/class-action-type-visibility.php (1)

44-52: LGTM! Secure whitelist validation.

The sanitization correctly validates visibility values against an allowed list with a safe fallback, using strict comparison for security.

src/action-types/class-action-type-text-color.php (1)

38-43: LGTM! Robust defensive check before sanitization.

The implementation correctly guards against non-array or missing 'color' key before sanitizing, which is safer than some sibling implementations (e.g., class-action-type-background-color.php at lines 55-58 which accesses $value['color'] without checking). This pattern should ideally be applied consistently across all action types.

src/action-types/class-action-type-toggle-class.php (1)

59-64: LGTM! Action validation is correct.

Allowlist validation with strict comparison and safe default fallback is the correct approach for enum-like values.

src/editor/components/timeline/property-control.js (1)

53-74: LGTM! Security restriction notice correctly implemented.

The conditional rendering logic is correct—showing the restriction notice only when both restrictedNotice exists and the user lacks unfiltered_html capability. The HTML tag stripping for the tooltip text is a reasonable approach.

src/action-types/abstract-action-type.php (1)

268-271: LGTM! Clean extension point for subclass sanitization.

The default no-op implementation with clear documentation provides a solid hook for action-type-specific sanitization logic.

src/action-types/class-action-type-opacity.php (1)

40-49: LGTM! Consistent numeric sanitization.

The implementation correctly follows the established pattern for numeric value sanitization with proper type checking and null fallback for invalid input.

src/class-interaction.php (3)

62-74: LGTM - Sanitization workflow properly integrated.

The changes correctly integrate the sanitization workflow: sanitizing data before persistence, returning early on errors, and using the sanitized data for post_content. The intentional use of $interaction_data for metadata fields (key, title, status) while using $sanitized_data for the serialized content is appropriate.


86-108: Verify the intended behavior for nested mixed-type arrays.

The sanitization logic correctly bypasses for unfiltered_html users and recursively handles arrays. However, note that after recursively processing an array, the subsequent is_string check on line 103 won't re-sanitize the array itself (which is correct), but intermediate non-string/non-array types (integers, booleans, objects) pass through unchanged. This is likely intentional, but verify this is the expected behavior for all action value types.


125-135: LGTM - Action value sanitization order is correct.

The sanitization applies general HTML sanitization first via sanitize_interaction_value, then type-specific sanitization via sanitize_data_for_saving. The WP_Error handling correctly propagates failures from type-specific sanitizers (like the dangerous attribute check in update-attribute). The sanitized value is properly written back and used for HMAC signing.

src/action-types/class-action-type-update-attribute.php (2)

33-33: LGTM - User-facing security notice added.

The restrictedNotice provides clear guidance to users about the permission requirements for certain attributes.


89-133: Verify intentional sanitization for users with unfiltered_html capability.

Sanitization via sanitize_key (line 100) and sanitize_style_value (line 105) is applied to all users before the unfiltered_html check at line 116. This means even administrators with unfiltered HTML capability will have:

  • Attribute names lowercased and special characters stripped (e.g., data:customAttrdatacustomattr)
  • Values processed through sanitize_style_value

If this is intentional (defense-in-depth), consider adding a brief comment. If admins should have full control, move the capability check earlier:

 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' )
         );
     }

+    // Admins with unfiltered_html bypass attribute/value sanitization but still get structure validation.
+    if ( current_user_can( 'unfiltered_html' ) ) {
+        // Still validate action field for consistency.
+        if ( isset( $value['action'] ) ) {
+            $allowed_actions = [ 'update', 'remove', 'toggle' ];
+            if ( ! in_array( $value['action'], $allowed_actions, true ) ) {
+                $value['action'] = 'update';
+            }
+        }
+        return $value;
+    }
+
     // Sanitize attribute name
     if ( isset( $value['attribute'] ) && is_string( $value['attribute'] ) ) {

Comment on lines +315 to +323
// 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' )
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for premium build references and related configuration
echo "=== Searching for premium build references ==="
rg -i "premium" scripts/package.js | head -20

echo ""
echo "=== Searching for Freemius or updater references ==="
rg -i "freemius|updater|basename" . | head -30

echo ""
echo "=== Searching for interactions.php references ==="
rg "interactions\.php" . --type js --type json --type php | head -30

echo ""
echo "=== Searching for plugin.php references ==="
rg "plugin\.php" . --type js --type json --type php | head -30

echo ""
echo "=== Checking for build output/distribution config ==="
rg -i "build|dist|package" . --type json -l | head -10

Repository: gambitph/Interactions

Length of output: 5119


🏁 Script executed:

#!/bin/bash
# Check package.json for build scripts and context
echo "=== package.json build scripts ==="
cat package.json | head -50

echo ""
echo "=== Looking for migration or update handling ==="
rg -i "migrate|upgrade|basename.*change|plugin.*main.*file" . --type php --type js | head -20

echo ""
echo "=== Check the full context around the rename in package.js ==="
sed -n '300,330p' scripts/package.js

Repository: gambitph/Interactions

Length of output: 5171


Rename from interactions.php to plugin.php breaks existing premium upgrade path and Freemius tracking

The rename creates a breaking change for existing premium installations. Previous premium releases have the plugin activated as interactions/interactions.php, but after this change, the main file becomes interactions/plugin.php.

Freemius tracks the plugin by plugin_basename($this->plugin_main_file), which will now return interactions/plugin.php instead of interactions/interactions.php. On upgrade, WordPress will have both files present—the old active file and the new main file—but Freemius and the plugin action links hook will reference the new basename, causing:

  • License verification to fail or become confused
  • Plugin action links not to appear correctly
  • Update checks to not recognize the existing installation

Either add a migration strategy (deactivate and reactivate the plugin during upgrade), document this as a breaking change requiring manual re-activation, or reconsider if renaming to plugin.php is necessary for the premium build.

🤖 Prompt for AI Agents
In scripts/package.js around lines 315-323, renaming interactions.php to
plugin.php breaks existing premium upgrade paths and Freemius tracking; instead
of renaming, change the build step to preserve interactions.php for backward
compatibility and also add the new plugin.php entrypoint: copy interactions.php
to plugin.php (do not delete the original) or create a small wrapper
interactions.php that loads/require_once plugin.php so
plugin_basename($this->plugin_main_file) still returns
interactions/interactions.php and existing installs continue to be recognized;
ensure both files are included in the BUILD_DIR and update the log message
accordingly.

Comment on lines +56 to +59
public function sanitize_data_for_saving( $value ) {
$value['color'] = $this->sanitize_style_value( $value['color'] );
return $value;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard $value['color'] access to avoid notices on malformed data

As with the CSS rule action, the use of sanitize_style_value is appropriate, but directly indexing $value['color'] assumes structure that might not always hold (filters, bad data, etc.), leading to PHP warnings.

A small defensive tweak keeps behavior while hardening against bad input:

-		public function sanitize_data_for_saving( $value ) {
-			$value['color'] = $this->sanitize_style_value( $value['color'] );
-			return $value;
-		}
+		public function sanitize_data_for_saving( $value ) {
+			if ( is_array( $value ) && isset( $value['color'] ) ) {
+				$value['color'] = $this->sanitize_style_value( $value['color'] );
+			}
+
+			return $value;
+		}

This matches the defensive style used in other action types while preserving the new sanitization.

🤖 Prompt for AI Agents
In src/action-types/class-action-type-background-color.php around lines 56 to
59, the code directly indexes $value['color'] which can trigger PHP notices if
$value is not an array or lacks the 'color' key; update the method to
defensively check that $value is an array and that 'color' exists (e.g. isset or
array_key_exists) before passing it to sanitize_style_value, and ensure the
function returns a sensible default (unchanged $value or null) when the key is
absent to preserve behavior without emitting warnings.

Comment on lines +37 to +40
public function sanitize_data_for_saving( $value ) {
$value['image'] = $this->sanitize_style_value( $value['image'] );
return $value;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add null checks before accessing array keys.

Lines 38 accesses $value['image'] without verifying that $value is an array or that the key exists. This will generate PHP notices if the key is not present.

Apply this diff for consistency with other sanitization methods:

 	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;
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function sanitize_data_for_saving( $value ) {
$value['image'] = $this->sanitize_style_value( $value['image'] );
return $value;
}
public function sanitize_data_for_saving( $value ) {
if ( is_array( $value ) && isset( $value['image'] ) ) {
$value['image'] = $this->sanitize_style_value( $value['image'] );
}
return $value;
}
🤖 Prompt for AI Agents
In src/action-types/class-action-type-background-image.php around lines 37 to
40, add null/structure checks before accessing $value['image']; currently the
code accesses the key directly which can raise notices if $value is not an array
or the key is missing. Update the method to first verify that $value is an array
(or cast to one) and that the 'image' key exists (or use isset) before calling
$this->sanitize_style_value, e.g. return early if $value is not an array or
lacks 'image', otherwise sanitize $value['image'] and return the array; follow
the same pattern used by other sanitize methods for consistency.

Comment on lines +44 to +47
public function sanitize_data_for_saving( $value ) {
$value['value'] = $this->sanitize_style_value( $value['value'] );
return $value;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add defensive checks before accessing $value['value']

The sanitization logic is good, but accessing $value['value'] without checking its shape can trigger PHP notices if $value isn’t an array or the key is missing (e.g., corrupted data, custom filters).

To match the more defensive pattern used in other action types (e.g., text color, redirect), you can guard the access:

-		public function sanitize_data_for_saving( $value ) {
-			$value['value'] = $this->sanitize_style_value( $value['value'] );
-			return $value;
-		}
+		public function sanitize_data_for_saving( $value ) {
+			if ( is_array( $value ) && isset( $value['value'] ) ) {
+				$value['value'] = $this->sanitize_style_value( $value['value'] );
+			}
+
+			return $value;
+		}

This keeps the sanitization behavior while avoiding runtime warnings on unexpected input shapes.

🤖 Prompt for AI Agents
In src/action-types/class-action-type-css-rule.php around lines 44 to 47, the
code directly accesses $value['value'] which can trigger PHP notices if $value
is not an array or the key is missing; add defensive checks before accessing the
key (e.g., if not is_array($value) return $value as-is; if the 'value' key is
not set, either set it to a safe default or return $value unchanged), then only
call $this->sanitize_style_value($value['value']) when the key exists; finally
return the (possibly modified) $value.

Comment on lines +67 to +96
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add null check before accessing array key.

Line 94 accesses $value['customTransformOrigin'] without verifying the key exists. This will generate a PHP notice if the key is not present.

Apply this diff to add the check:

 		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'] );
+		if ( isset( $value['customTransformOrigin'] ) ) {
+			$value['customTransformOrigin'] = $this->sanitize_style_value( $value['customTransformOrigin'] );
+		}
 		return $value;
🤖 Prompt for AI Agents
In src/action-types/class-action-type-rotate.php around lines 67 to 96, the code
accesses $value['customTransformOrigin'] on line 94 without verifying the key
exists; update the logic to check that $value is an array and that the
'customTransformOrigin' key exists (use isset or array_key_exists) before
calling $this->sanitize_style_value on it, and if the key is missing set
$value['customTransformOrigin'] to null (or a safe default) so no PHP notice is
raised.

Comment on lines +637 to +651
wp.ajax.post( 'interactfa_activate', { license_key: btoa( licenseKey ), nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
console.log('result', result);
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
}).fail( function ( error ) {
result.classList.remove( 'success' )
result.classList.add( 'err' )
result.textContent = error;
dialog.classList.remove( 'busy' )
dialog.querySelector( 'button.close' ).disabled = false
dialog.querySelector( 'button.activate' ).disabled = false
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statements.

Debug statements left in production code. These should be removed.

 			wp.ajax.post( 'interactfa_activate', { license_key: btoa( licenseKey ), nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
 				result.classList.remove( 'err' )
 				result.classList.add( 'success' )
 				result.textContent = success;
-				console.log('result', result);
 				dialog.classList.remove( 'busy' )
 				setTimeout( function() { window.location.reload() }, 1000 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wp.ajax.post( 'interactfa_activate', { license_key: btoa( licenseKey ), nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
console.log('result', result);
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
}).fail( function ( error ) {
result.classList.remove( 'success' )
result.classList.add( 'err' )
result.textContent = error;
dialog.classList.remove( 'busy' )
dialog.querySelector( 'button.close' ).disabled = false
dialog.querySelector( 'button.activate' ).disabled = false
});
wp.ajax.post( 'interactfa_activate', { license_key: btoa( licenseKey ), nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
}).fail( function ( error ) {
result.classList.remove( 'success' )
result.classList.add( 'err' )
result.textContent = error;
dialog.classList.remove( 'busy' )
dialog.querySelector( 'button.close' ).disabled = false
dialog.querySelector( 'button.activate' ).disabled = false
});
🤖 Prompt for AI Agents
In src/freemius.php around lines 637 to 651, there is a leftover debug
console.log('result', result); in the AJAX success handler; remove that
console.log statement (and any other stray debug console.log calls in this
block) so the production JS contains no debug logging, keeping the rest of the
success handler intact.

Comment on lines +660 to 666
wp.ajax.post( 'interactfa_deactivate', { nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
console.log('result', result);
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statement.

Same issue as in the activate handler - debug statement should be removed.

 		wp.ajax.post( 'interactfa_deactivate', { nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
 			result.classList.remove( 'err' )
 			result.classList.add( 'success' )
 			result.textContent = success;
-			console.log('result', result);
 			dialog.classList.remove( 'busy' )
 			setTimeout( function() { window.location.reload() }, 1000 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
wp.ajax.post( 'interactfa_deactivate', { nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
console.log('result', result);
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
wp.ajax.post( 'interactfa_deactivate', { nonce: "{$esc_attr( $nonce )}" } ).done( function( success ) {
result.classList.remove( 'err' )
result.classList.add( 'success' )
result.textContent = success;
dialog.classList.remove( 'busy' )
setTimeout( function() { window.location.reload() }, 1000 )
🤖 Prompt for AI Agents
In src/freemius.php around lines 660 to 666, there's a leftover debug
console.log('result', result); in the deactivate AJAX done handler; remove that
console.log line so no debugging output is emitted to the browser console,
leaving the remaining DOM updates and reload logic intact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants