Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: new builder stage form builder UI redesign #1503

Open
wants to merge 27 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
63151b3
initial header style
sapayth Oct 7, 2024
787c082
preview and save buttons
sapayth Oct 7, 2024
a7c5fe7
dropdown effect enhanced
sapayth Oct 9, 2024
d008e8a
shortcode copy icon enhanced
sapayth Oct 9, 2024
c96afbf
backward compatibility added for old wpuf pro
sapayth Oct 10, 2024
065b1fa
update copy shortcode icon
sapayth Oct 10, 2024
6607241
Merge branch 'develop' into enhance/header_for_form_builder_ui_redesign
sapayth Oct 21, 2024
ab3ce2c
type and image fixing
sapayth Oct 22, 2024
3eba5a0
Merge remote-tracking branch 'upstream/develop' into enhance/header_f…
sapayth Oct 31, 2024
7293f64
updated form builder header design
sapayth Nov 8, 2024
fe34c3b
nav css
sapayth Nov 8, 2024
ab36c5c
Squashed commit of the following:
sapayth Nov 8, 2024
6a93fc4
Merge remote-tracking branch 'upstream/develop' into enhance/new_buil…
sapayth Nov 11, 2024
dbef1af
initial design
sapayth Nov 12, 2024
ab32ffd
hidden fields and other small css
sapayth Nov 13, 2024
ed4b378
column field
sapayth Nov 13, 2024
d02c221
Merge remote-tracking branch 'upstream/develop' into enhance/new_buil…
sapayth Nov 13, 2024
bc50028
Merge remote-tracking branch 'upstream/develop' into enhance/new_buil…
sapayth Nov 14, 2024
3b59cbc
column fields initial rendering
sapayth Nov 15, 2024
4443867
column field initial
sapayth Nov 25, 2024
aa1c0d8
fix action buttons move action
sapayth Nov 25, 2024
aa9f072
icon for featured image
sapayth Nov 25, 2024
21d19c2
icon for image upload button
sapayth Nov 25, 2024
87f577d
Merge remote-tracking branch 'upstream/develop' into enhance/new_buil…
sapayth Nov 26, 2024
ac5db7e
backward compatibility for pro version
sapayth Nov 26, 2024
3df5eeb
compatibility for settings and notification tab
sapayth Nov 26, 2024
41e4e17
fix css feedback
sapayth Nov 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
'use strict';
module.exports = function(grunt) {
module.exports = function( grunt) {
const tailwindFileMap = {
'admin/form-builder/views/form-builder.php': 'admin/form-builder.css',
}

var formBuilderAssets = require('./admin/form-builder/assets/js/form-builder-assets.js');

var pkg = grunt.file.readJSON('package.json');
Expand Down Expand Up @@ -112,7 +116,21 @@ module.exports = function(grunt) {
tasks: [
'shell:npm_build'
]
}
},

tailwind: {
files: [
'src/css/**/*.css',
'admin/form-builder/views/*.php',
'admin/form-builder/assets/js/**/*.php',
'admin/form-builder/assets/js/**/*.js',
'includes/Admin/**/*.php',
],
tasks: ['shell:tailwind'],
options: {
spawn: false
}
},
Comment on lines +121 to +133
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Inconsistency between 'watch' task and event handler for 'tailwind'

The watch task for tailwind specifies tasks: ['shell:tailwind'], but in the event listener, the task grunt.task.run('tailwind') is called when changes are detected. This may lead to confusion or redundancy.

Suggestion:

  • If using the event listener to handle the task execution, you can remove the tasks array from the watch configuration:

    tailwind: {
        files: [
            'src/css/**/*.css',
            'admin/form-builder/views/*.php',
            'admin/form-builder/assets/js/**/*.php',
            'admin/form-builder/assets/js/**/*.js',
            'includes/Admin/**/*.php',
        ],
    -   tasks: ['shell:tailwind'],
    +   tasks: [],
        options: {
            spawn: false
        }
    },
  • Alternatively, if you prefer to use the tasks array, you can remove the event listener and ensure the correct task is specified:

    tailwind: {
        files: [
            'src/css/**/*.css',
            'admin/form-builder/views/*.php',
            'admin/form-builder/assets/js/**/*.php',
            'admin/form-builder/assets/js/**/*.js',
            'includes/Admin/**/*.php',
        ],
    -   tasks: ['shell:tailwind'],
    +   tasks: ['tailwind'],
        options: {
            spawn: false
        }
    },

Committable suggestion skipped: line range outside the PR's diff.

},

// Clean up build directory
Expand Down Expand Up @@ -224,6 +242,11 @@ module.exports = function(grunt) {
shell: {
npm_build: {
command: 'npm run build',
},
tailwind: {
command: function ( input, output ) {
return `npx tailwindcss -i ${input} -o ${output}`;
}
}
}
});
Expand All @@ -241,6 +264,7 @@ module.exports = function(grunt) {
grunt.loadNpmTasks( 'grunt-notify' );
grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' );
grunt.loadNpmTasks( 'grunt-shell' );
grunt.loadNpmTasks( 'grunt-postcss' );
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unused 'grunt-postcss' task loader detected

You have loaded the grunt-postcss task with grunt.loadNpmTasks('grunt-postcss');, but there is no corresponding postcss task configured in the grunt.initConfig. This may be unnecessary if postcss is not being used.

Suggestion:

  • If grunt-postcss is not needed, remove the task loader:

    - grunt.loadNpmTasks('grunt-postcss');
  • If you intended to use postcss, make sure to configure the postcss task appropriately.

Committable suggestion skipped: line range outside the PR's diff.


grunt.registerTask( 'default', [ 'less', 'concat', 'uglify', 'i18n' ] );

Expand All @@ -251,4 +275,28 @@ module.exports = function(grunt) {
// build stuff
grunt.registerTask( 'release', [ 'less', 'concat', 'uglify', 'i18n', 'readme' ] );
grunt.registerTask( 'zip', [ 'clean', 'copy', 'compress' ] );

grunt.event.on('watch', function(action, filepath, target) {
if (target === 'tailwind') {
grunt.task.run('tailwind');
}
});

grunt.registerTask('tailwind', function() {
const done = this.async();

// Process each file mapping
Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
const inputFile = `src/css/${cssFile}`;
const outputFile = `assets/css/${cssFile}`;

// Ensure the input file exists
if (grunt.file.exists(inputFile)) {
// Run the tailwind command
grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
}
});

done();
});
Comment on lines +285 to +301
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance the Tailwind task implementation.

The current implementation could benefit from:

  1. Error handling for file operations
  2. Progress logging
  3. Parallel processing for multiple files
     grunt.registerTask('tailwind', function() {
         const done = this.async();
+        let errors = [];
 
         // Process each file mapping
-        Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
+        const promises = Object.entries(tailwindFileMap).map(([phpFile, cssFile]) => {
             const inputFile = `src/css/${cssFile}`;
             const outputFile = `assets/css/${cssFile}`;
 
-            // Ensure the input file exists
-            if (grunt.file.exists(inputFile)) {
-                // Run the tailwind command
-                grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
-            }
+            return new Promise((resolve) => {
+                if (!grunt.file.exists(inputFile)) {
+                    grunt.log.error(`Input file ${inputFile} does not exist`);
+                    errors.push(`Missing input file: ${inputFile}`);
+                    resolve();
+                    return;
+                }
+                
+                grunt.log.write(`Processing ${inputFile}...`);
+                grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
+                grunt.log.ok();
+                resolve();
+            });
         });
 
-        done();
+        Promise.all(promises).then(() => {
+            if (errors.length > 0) {
+                grunt.log.error('Tailwind processing completed with errors:');
+                errors.forEach(error => grunt.log.error(error));
+                done(false);
+            } else {
+                grunt.log.ok('Tailwind processing completed successfully');
+                done();
+            }
+        });
     });
📝 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
grunt.registerTask('tailwind', function() {
const done = this.async();
// Process each file mapping
Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
const inputFile = `src/css/${cssFile}`;
const outputFile = `assets/css/${cssFile}`;
// Ensure the input file exists
if (grunt.file.exists(inputFile)) {
// Run the tailwind command
grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
}
});
done();
});
grunt.registerTask('tailwind', function() {
const done = this.async();
let errors = [];
// Process each file mapping
const promises = Object.entries(tailwindFileMap).map(([phpFile, cssFile]) => {
const inputFile = `src/css/${cssFile}`;
const outputFile = `assets/css/${cssFile}`;
return new Promise((resolve) => {
if (!grunt.file.exists(inputFile)) {
grunt.log.error(`Input file ${inputFile} does not exist`);
errors.push(`Missing input file: ${inputFile}`);
resolve();
return;
}
grunt.log.write(`Processing ${inputFile}...`);
grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
grunt.log.ok();
resolve();
});
});
Promise.all(promises).then(() => {
if (errors.length > 0) {
grunt.log.error('Tailwind processing completed with errors:');
errors.forEach(error => grunt.log.error(error));
done(false);
} else {
grunt.log.ok('Tailwind processing completed successfully');
done();
}
});
});

Comment on lines +285 to +301
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Asynchronous handling in 'tailwind' task may cause premature completion

In the tailwind task, this.async() is called to handle asynchronous operations, but done() is called immediately after scheduling the shell tasks with grunt.task.run(). This may result in the task completing before the shell commands have finished executing.

Suggestion:

  • Remove the asynchronous handling since grunt.task.run() schedules the tasks to run after the current task completes, and there is no need for this.async() and done() in this context.

    grunt.registerTask('tailwind', function() {
    -   const done = this.async();
    
        // Process each file mapping
        Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
            const inputFile = `src/css/${cssFile}`;
            const outputFile = `assets/css/${cssFile}`;
    
            // Ensure the input file exists
            if (grunt.file.exists(inputFile)) {
                // Run the tailwind command
                grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
            }
        });
    
    -   done();
    });
  • If the shell commands are asynchronous and you need to ensure that all tasks have completed before finishing, consider using grunt.task.run() with proper asynchronous handling or use promises to track task completion.

📝 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
grunt.registerTask('tailwind', function() {
const done = this.async();
// Process each file mapping
Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
const inputFile = `src/css/${cssFile}`;
const outputFile = `assets/css/${cssFile}`;
// Ensure the input file exists
if (grunt.file.exists(inputFile)) {
// Run the tailwind command
grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
}
});
done();
});
grunt.registerTask('tailwind', function() {
// Process each file mapping
Object.entries(tailwindFileMap).forEach(([phpFile, cssFile]) => {
const inputFile = `src/css/${cssFile}`;
const outputFile = `assets/css/${cssFile}`;
// Ensure the input file exists
if (grunt.file.exists(inputFile)) {
// Run the tailwind command
grunt.task.run(`shell:tailwind:${inputFile}:${outputFile}`);
}
});
});

};
221 changes: 166 additions & 55 deletions admin/form-builder/assets/js/components/builder-stage/template.php

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
<div class="wpuf-fields">
<ul :class="['wpuf-fields-list', ('yes' === field.inline) ? 'wpuf-list-inline' : '']">
<li v-if="has_options" v-for="(label, val) in field.options">
<label>
<div
v-if="field.inline !== 'yes'"
class="wpuf-space-y-2">
<div
v-if="has_options" v-for="(label, val) in field.options"
class="wpuf-relative wpuf-flex wpuf-items-center">
<div class="wpuf-flex wpuf-items-center">
<input
type="checkbox"
:value="val"
:checked="is_selected(val)"
:class="class_names('checkbox_btns')"
> {{ label }}
</label>
</li>
</ul>
class="wpuf-h-4 wpuf-w-4 wpuf-rounded wpuf-border-gray-300 wpuf-text-indigo-600 focus:wpuf-ring-indigo-600 !wpuf-mt-0.5">
<label class="wpuf-ml-3 wpuf-text-sm wpuf-font-medium wpuf-text-gray-900">{{ label }}</label>
</div>
</div>
</div>

<span v-if="field.help" class="wpuf-help" v-html="field.help" />
<div
v-else
class="wpuf-flex"
>
<div
v-if="has_options" v-for="(label, val) in field.options"
class="wpuf-relative wpuf-flex wpuf-items-center wpuf-mr-4">
<input
type="checkbox"
:value="val"
:checked="is_selected(val)"
:class="class_names('checkbox_btns')"
class="!wpuf-mt-[.5px] wpuf-rounded wpuf-border-gray-300 wpuf-text-indigo-600">
<label class="wpuf-ml-1 wpuf-text-sm wpuf-font-medium wpuf-text-gray-900">{{ label }}</label>
</div>
</div>

<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the search results, I can now provide a final response since I have enough information about how the help text is handled in the codebase:

Help text is not sanitized before rendering with v-html

The help text is directly rendered using v-html without any sanitization, which could allow XSS attacks if the help text contains malicious HTML/JavaScript. The codebase shows no evidence of sanitization being applied to field.help before rendering.

  • field.help is directly used with v-html in multiple components:
    • admin/form-builder/assets/js/components/form-checkbox_field/template.php
    • admin/form-builder/assets/js/components/form-text_field/template.php
    • And many other form field components

Consider:

  1. Sanitizing help text when saving field configuration
  2. Using a sanitization utility before rendering with v-html
  3. Or using v-text with manual HTML entity encoding if formatted text is not required
🔗 Analysis chain

Verify help text sanitization to prevent XSS.

While using v-html is necessary for formatted help text, ensure that field.help is properly sanitized before rendering to prevent XSS attacks.

Let's check how the help text is processed:


Let me generate additional scripts to check how the help text is processed before being rendered:


Let me generate one final search to check for any sanitization or processing of field data before rendering:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for help text sanitization in the codebase
# Look for sanitization functions or methods that process field.help

# Search for help text processing
rg -A 5 "field\.help" --type php --type js

# Search for sanitization functions
ast-grep --pattern 'function sanitize$_($$$) { $$$ }'

Length of output: 23285


Script:

#!/bin/bash
# Search for where field.help is set/processed
rg -B 5 -A 5 "field\.help\s*=" --type js

# Look for sanitization or processing of field data
rg -B 5 -A 5 "(sanitize|process|clean).*field" --type js

# Check form builder initialization and field processing
ast-grep --pattern 'field = { $$$ help: $$$ }'

Length of output: 128584


Script:

#!/bin/bash
# Search for field data processing or sanitization
rg -B 3 -A 3 "field\.(help|data)" --type php --type js

# Look for any escaping or sanitization utilities
rg -B 2 -A 2 "(escape|sanitize|clean).*html" --type php --type js

Length of output: 117792

Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Based on the search results, I can now generate the final response:

Help text needs server-side sanitization before storage

The help text is rendered using v-html directive in Vue templates, but there's no clear evidence of sanitization before storage. While form fields have sanitization methods (like sanitize_field_data), the help text appears to be directly stored and rendered.

To prevent XSS risks:

  • Help text should be sanitized on the server side before storage using WordPress functions like wp_kses_post()
  • Consider using v-text or interpolation {{ }} instead of v-html if rich HTML is not required
🔗 Analysis chain

Verify help text sanitization for XSS prevention.

While the implementation looks good, using v-html with unsanitized content could pose security risks.

Run the following script to check if help text is properly sanitized before being stored:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for help text sanitization in the codebase

# Look for sanitization functions being applied to field.help
rg -A 5 "field\.help.*=.*sanitize"

# Check for any existing XSS prevention utilities
ast-grep --pattern 'function $_($$sanitize|escape|purify)$_($$$) { $$$ }'

Length of output: 112


Script:

#!/bin/bash
# Let's broaden our search to understand how field.help is populated and handled

# Check for field.help assignments or modifications
rg -A 5 "field\.help\s*=" 

# Look for any sanitization or escaping utilities in Vue components
rg -B 2 -A 5 "sanitize|escape|purify|xss"

# Check form field definitions and how help text is processed
fd "form" --type f --exec rg -l "help.*text"

# Look specifically in the form builder components
fd "form-builder" --type f --exec rg -l "field\.help"

Length of output: 131176


Script:

#!/bin/bash
# Let's check how field.help is populated and if there's any sanitization before it reaches the template

# Look for field.help assignments in form builder components
rg -A 5 "field\.help\s*=" admin/form-builder/

# Check for any sanitization or escaping in the form builder Vue components
rg -B 2 -A 5 "v-html.*help" admin/form-builder/

# Look for any help text processing in the form builder
rg -B 2 -A 5 "help.*text.*sanitize|help.*text.*escape" admin/form-builder/

# Check if there's any global sanitization for form fields
rg -B 2 -A 5 "sanitize.*field|escape.*field" admin/form-builder/

Length of output: 7280


Script:

#!/bin/bash
# Let's check how the form builder data is processed before being rendered

# Look for form field processing in the backend
rg -B 2 -A 5 "process.*form|handle.*form" includes/

# Check for any sanitization in the form submission handling
rg -B 2 -A 5 "sanitize.*before|escape.*before" includes/

# Look for Vue component initialization and data processing
rg -B 2 -A 5 "data\(\)|mounted\(\)" admin/form-builder/assets/js/components/

# Check for any global form field sanitization functions
rg -B 2 -A 5 "sanitize_field|escape_field|clean_field" includes/

Length of output: 13697

</div>
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ Vue.component('form-column_field', {
self.resizeColumns(self.field.columns);
});


columnFieldArea.mouseleave(function() {
columnFields.unbind( "mouseup" );
columnFields.unbind( "mousemove" );
Expand Down Expand Up @@ -120,6 +119,10 @@ Vue.component('form-column_field', {
field_settings: function () {
return this.$store.state.field_settings;
},

action_button_classes: function() {
return 'hover:wpuf-cursor-pointer hover:wpuf-text-white';
}
},

methods: {
Expand Down
155 changes: 114 additions & 41 deletions admin/form-builder/assets/js/components/form-column_field/template.php
Original file line number Diff line number Diff line change
@@ -1,47 +1,120 @@
<div v-bind:class="['wpuf-field-columns', 'has-columns-'+field.columns]">
<div class="wpuf-column-field-inner-columns">
<div class="wpuf-column">
<!-- don't change column class names -->
<div v-for="column in columnClasses" :class="[column, 'items-of-column-'+field.columns, 'wpuf-column-inner-fields']" :style="{ width: field.inner_columns_size[column], paddingRight: field.column_space+'px'}">
<ul class="wpuf-column-fields-sortable-list">
<li
v-for="(field, index) in column_fields[column]"
:key="field.id"
:class="[
'column-field-items', 'wpuf-el', field.name, field.css, 'form-field-' + field.template,
field.width ? 'field-size-' + field.width : '',
parseInt(editing_form_id) === parseInt(field.id) ? 'current-editing' : ''
]"
:column-field-index="index"
:in-column="column"
data-source="column-field-stage"
>
<div v-if="!is_full_width(field.template)" class="wpuf-label wpuf-column-field-label">
<label v-if="!is_invisible(field)" :for="'wpuf-' + field.name ? field.name : 'cls'">
{{ field.label }} <span v-if="field.required && 'yes' === field.required" class="required">*</span>
<div :class="['wpuf-field-columns wpuf-flex wpuf-flex-col md:wpuf-flex-row wpuf-gap-4 wpuf-p-4 wpuf-w-full', 'has-columns-'+field.columns]">
<div
v-for="column in columnClasses"
class="wpuf-flex-1 wpuf-min-w-0 wpuf-min-h-full">
<div class="wpuf-column-inner-fields wpuf-border wpuf-border-dashed wpuf-border-green-400 wpuf-bg-green-50 wpuf-shadow-sm wpuf-rounded-md wpuf-p-1">
<ul class="wpuf-column-fields-sortable-list">
<li
v-for="(field, innerIndex) in column_fields[column]"
:key="field.id"
:column-field-index="innerIndex"
:in-column="column"
Comment on lines +8 to +10
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Update Required: Mixed Usage of index and innerIndex

The verification reveals inconsistent usage between template and script:

  • Template uses innerIndex in the v-for directive
  • Component methods still use index parameter in their definitions:
    • open_column_field_settings(field, index, column)
    • clone_column_field(field, index, column)
    • delete_column_field(index, fromColumn)

These method definitions need to be updated to use innerIndex to match the template usage.

🔗 Analysis chain

Ensure Consistent Use of innerIndex

You've replaced index with innerIndex in the v-for directive and related method calls. Please verify that all instances of index within the component have been updated to innerIndex to prevent any potential reference errors.

Run the following script to find any remaining uses of index within the component:

Also applies to: 67-67, 73-73, 93-93

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find occurrences of 'index' in the component files.

# Search for 'index' in the template files
rg 'index' admin/form-builder/assets/js/components/form-column_field/template.php

# Search for 'index' in the script files
rg 'index' admin/form-builder/assets/js/components/form-column_field/index.js

Length of output: 722

data-source="column-field-stage"
class="!wpuf-m-0 !wpuf-p-0 wpuf-group/column-inner hover:wpuf-bg-green-50 wpuf-transition wpuf-duration-150 wpuf-ease-out column-field-items wpuf-el wpuf-rounded-t-md"
:class="[
field.name,
field.css,
'form-field-' + field.template,
field.width ? 'field-size-' + field.width : '',
Comment on lines +14 to +17
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure 'field.css' is sanitized to prevent CSS injection

Including field.css directly in the :class binding may pose a security risk if field.css can be manipulated by users. Ensure that field.css is properly sanitized and only contains valid, expected class names to prevent CSS injection attacks.

('custom_hidden_field' === field.template) ? 'hidden-field' : '',
parseInt(editing_form_id) === parseInt(field.id) ? 'current-editing' : ''
]">
<div class="wpuf-flex wpuf-flex-col md:wpuf-flex-row wpuf-gap-2 wpuf-p-4 wpuf-border-transparent group-hover/column-inner:wpuf-border-green-400 wpuf-rounded-t-md wpuf-border-t wpuf-border-r wpuf-border-l wpuf-border-dashed wpuf-border-green-400">
<div
v-if="!(is_full_width(field.template) || is_pro_feature(field.template))"
class="wpuf-w-full md:wpuf-w-1/4 wpuf-shrink-0">
<label v-if="!is_invisible(field)"
:for="'wpuf-' + (field.name ? field.name : 'cls')"
class="wpuf-block wpuf-text-sm">
{{ field.label }}
<span v-if="field.required && 'yes' === field.required"
class="required">*</span>
</label>
</div>

<component v-if="is_template_available(field)" :is="'form-' + field.template" :field="field"></component>

<div v-if="is_pro_feature(field.template)" class="stage-pro-alert">
<label class="wpuf-pro-text-alert">
<a :href="pro_link" target="_blank"><strong>{{ get_field_name(field.template) }}</strong> <?php _e( 'is available in Pro Version', 'wp-user-frontend' ); ?></a>
</label>
<div
:class="[
'wpuf-relative wpuf-min-w-0', // Added wpuf-min-w-0
(is_full_width(field.template) || is_pro_feature(field.template))
? 'wpuf-w-full'
: 'wpuf-w-full md:wpuf-w-3/4'
]">
<div class="wpuf-absolute wpuf-w-full wpuf-h-full wpuf-z-10"></div>
<div class="wpuf-relative">
<component
v-if="is_template_available(field)"
:is="'form-' + field.template"
:field="field">
</component>
<div v-if="is_pro_feature(field.template)" class="stage-pro-alert wpuf-text-center">
<label class="wpuf-pro-text-alert">
<a :href="pro_link" target="_blank"
class="wpuf-text-gray-700 wpuf-text-base"><strong>{{ get_field_name( field.template )
}}</strong> <?php _e( 'is available in Pro Version', 'wp-user-frontend' ); ?></a>
</label>
</div>
</div>
</div>

<div class="wpuf-column-field-control-buttons">
<p>
<i class="fa fa-arrows move"></i>
<i class="fa fa-pencil" @click="open_column_field_settings(field, index, column)"></i>
<i class="fa fa-clone" @click="clone_column_field(field, index, column)"></i>
<i class="fa fa-trash-o" @click="delete_column_field(index, column)"></i>
</p>
</div>
<div
class="wpuf-column-field-control-buttons wpuf-opacity-0 group-hover/column-inner:wpuf-opacity-100 wpuf-rounded-b-lg wpuf-bg-green-600 wpuf-items-center wpuf-transition wpuf-duration-150 wpuf-ease-out">
<div class="wpuf-items-center wpuf-text-green-200 wpuf-flex wpuf-justify-evenly wpuf-p-1">
<template v-if="!is_failed_to_validate(field.template)">
<span :class="action_button_classes">
<i
class="fa fa-arrows move wpuf-pr-2 wpuf-rounded-l-md hover:!wpuf-cursor-move wpuf-border-r wpuf-border-green-200"></i>
</span>
<span :class="action_button_classes"
@click="open_column_field_settings(field, innerIndex, column)">
<i
class="fa fa-pencil"></i>
Edit
</span>
<span :class="action_button_classes"
@click="clone_column_field(field, innerIndex, column)">
<i
class="fa fa-clone"></i>
Copy
</span>
</template>
<template v-else>
<span :class="action_button_classes">
<i class="fa fa-arrows control-button-disabled wpuf--ml-1 wpuf-rounded-l-md"></i>
</span>
<span :class="action_button_classes">
<i class="fa fa-pencil control-button-disabled wpuf--ml-1"></i>
Edit
</span>
<span :class="action_button_classes">
<i
class="fa fa-clone control-button-disabled wpuf--ml-1"></i>
Copy
</span>
</template>
<span :class="action_button_classes" @click="delete_column_field(innerIndex, column)">
<i
class="fa fa-trash-o wpuf--ml-1"></i>
Remove
</span>
<span :class="action_button_classes"
v-if="is_pro_feature(field.template)"
class="hover:wpuf-bg-green-700">
<a
:href="pro_link"
target="_blank"
class="wpuf-rounded-r-md hover:wpuf-bg-slate-500 hover:wpuf-cursor-pointer wpuf-transition wpuf-duration-150 wpuf-ease-out hover:wpuf-transition-all">
<svg
Comment on lines +99 to +104
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Duplicate class Attribute Detected

The <span> element at lines 99-104 contains two class attributes:

<span :class="action_button_classes"
    v-if="is_pro_feature(field.template)"
    class="hover:wpuf-bg-green-700">

This can lead to unexpected behavior, as HTML elements should not have duplicate attributes.

Apply this diff to merge the classes into a single :class binding:

-<span :class="action_button_classes"
-    v-if="is_pro_feature(field.template)"
-    class="hover:wpuf-bg-green-700">
+<span
+    v-if="is_pro_feature(field.template)"
+    :class="[action_button_classes, 'hover:wpuf-bg-green-700']">

width="15" height="15" viewBox="0 0 20 15" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M19.2131 4.11564C19.2161 4.16916 19.2121 4.22364 19.1983 4.27775L17.9646 10.5323C17.9024 10.7741 17.6796 10.9441 17.4235 10.9455L10.0216 10.9818H10.0188H2.61682C2.35933 10.9818 2.13495 10.8112 2.07275 10.5681L0.839103 4.29542C0.824897 4.23985 0.820785 4.18385 0.824374 4.12895C0.34714 3.98269 0 3.54829 0 3.03636C0 2.40473 0.528224 1.89091 1.17757 1.89091C1.82692 1.89091 2.35514 2.40473 2.35514 3.03636C2.35514 3.39207 2.18759 3.71033 1.92523 3.92058L3.46976 5.43433C3.86011 5.81695 4.40179 6.03629 4.95596 6.03629C5.61122 6.03629 6.23596 5.7336 6.62938 5.22647L9.1677 1.95491C8.95447 1.74764 8.82243 1.46124 8.82243 1.14545C8.82243 0.513818 9.35065 0 10 0C10.6493 0 11.1776 0.513818 11.1776 1.14545C11.1776 1.45178 11.0526 1.72982 10.8505 1.93556L10.8526 1.93811L13.3726 5.21869C13.7658 5.73069 14.3928 6.03636 15.0499 6.03636C15.6092 6.03636 16.1351 5.82451 16.5305 5.43978L18.0848 3.92793C17.8169 3.71775 17.6449 3.39644 17.6449 3.03636C17.6449 2.40473 18.1731 1.89091 18.8224 1.89091C19.4718 1.89091 20 2.40473 20 3.03636C20 3.53462 19.6707 3.9584 19.2131 4.11564ZM17.8443 12.6909C17.8443 12.3897 17.5932 12.1455 17.2835 12.1455H2.77884C2.46916 12.1455 2.21809 12.3897 2.21809 12.6909V14C2.21809 14.3012 2.46916 14.5455 2.77884 14.5455H17.2835C17.5932 14.5455 17.8443 14.3012 17.8443 14V12.6909Z"
fill="#FB9A28"/>
</svg>
</a>
</span>
</div>
</li>

</ul>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="wpuf-fields">
<input
type="text"
:class="class_names('textfield')"
:class="builder_class_names('text_hidden')"
:placeholder="field.placeholder"
:value="field.default"
:size="field.size"
>
<span v-if="field.help" class="wpuf-help" v-html="field.help" />
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
</div>
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
<div class="wpuf-fields">
<select
:class="class_names('select_lbl')"
>
class="wpuf-block wpuf-w-full wpuf-min-w-full wpuf-rounded-md wpuf-py-1.5 wpuf-text-gray-900 wpuf-shadow-sm placeholder:wpuf-text-gray-400 sm:wpuf-text-sm sm:wpuf-leading-6 wpuf-border !wpuf-border-gray-300">
<option v-if="field.first" value="">{{ field.first }}</option>

<option
v-if="has_options"
v-for="(label, val) in field.options"
:value="label"
:selected="is_selected(label)"
>{{ label }}</option>
</select>

<span v-if="field.help" class="wpuf-help" v-html="field.help"> </span>
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
</div>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="wpuf-fields">
<input
type="email"
:class="class_names('email')"
:class="class_names('email') + builder_class_names('text')"
:placeholder="field.placeholder"
:value="field.default"
:size="field.size"
>
<span v-if="field.help" class="wpuf-help" v-html="field.help" />
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Potential XSS vulnerability in help text rendering

Using v-html directive with field.help could be dangerous if the help text contains user input. Consider using v-text or {{ }} interpolation if HTML rendering is not strictly necessary.

-    <p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
+    <p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-text="field.help"></p>
📝 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
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-text="field.help"></p>

</div>
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<div class="wpuf-fields">
<div :id="'wpuf-img_label-' + field.id + '-upload-container'">
<div class="wpuf-attachment-upload-filelist" data-type="file" data-required="yes">
<a class="button file-selector" href="#">
<a class="wpuf-inline-flex wpuf-items-center wpuf-gap-x-1.5"
:class="builder_class_names('upload_btn')" href="#">
<template v-if="field.button_label === ''">
<?php _e( 'Select Image', 'wp-user-frontend' ); ?>
</template>
<template v-else>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="wpuf-size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessibility attributes to SVG icon

While the SVG icon enhances visual feedback, it needs accessibility improvements for screen readers.

-    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="wpuf-size-5">
+    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="wpuf-size-5" 
+        role="img" aria-label="Upload image">
+        <title>Upload image</title>
         <path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
     </svg>
📝 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
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="wpuf-size-5">
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="wpuf-size-5"
role="img" aria-label="Upload image">
<title>Upload image</title>
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>

{{ field.button_label }}
</template>
</a>
</div>
</div>

<span v-if="field.help" class="wpuf-help" v-html="field.help" />
<p v-if="field.help" class="wpuf-mt-2 wpuf-text-sm wpuf-text-gray-500" v-html="field.help"></p>
</div>
Loading
Loading