Skip to content

add filter that will extended the form field container html attributes…#2428

Merged
Crabcyborg merged 13 commits into
masterfrom
flatpickr/date-time-ranges
Aug 11, 2025
Merged

add filter that will extended the form field container html attributes…#2428
Crabcyborg merged 13 commits into
masterfrom
flatpickr/date-time-ranges

Conversation

@Liviu-p
Copy link
Copy Markdown
Contributor

@Liviu-p Liviu-p commented Jul 16, 2025

…, add js filter that will get fired after a field is successfully added in the form build, export 2 js functions that are related to inserting a new field and deleting it from form builder in order to reuse these in other plugins, toggle a jQuery notice if jQuery is selected as library from general settings

This PR is a requirement for https://github.com/Strategy11/formidable-pro/pull/5902 and https://github.com/Strategy11/formidable-dates/pull/168.

…, add js filter that will get fired after a field is successfully added in the form build, export 2 js functions that are related to inserting a new field and deleting it from form builder in order to reuse these in other plugins, toggle a jQuery notice if jQuery is selected as library from general settings
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jul 16, 2025

Walkthrough

Adds optional field_options to new-field creation in PHP and JavaScript, introduces a filter for extra field container attributes, updates the view to render those attributes, adds a default range_field setting, enhances admin JS with an insertFormField API and related hooks, and adjusts datepicker loading to be conditional on Pro capabilities.

Changes

Cohort / File(s) Summary of changes
Field creation with options and extra attributes
classes/controllers/FrmFieldsController.php, classes/views/frm-forms/add_field.php
PHP: include_new_field now accepts optional field_options and merges them; create() reads sanitized field_options from POST; adds filter frm_field_container_extra_attributes used to compute extra attributes. View:
  • now outputs dynamic extra attributes.
  • Default field settings
    classes/models/fields/FrmFieldType.php
    Adds 'range_field' => false to default_field_settings() return array.
    Admin builder JS API and hooks
    js/formidable_admin.js
    Adds insertFormField(fieldType, fieldOptions) Promise-based API; extracts fieldId from response; fires frmadmin.afterFieldAddedInFormBuilder; exports deleteField, insertFormField, confirmLinkClick; minor change to confirmation modal link handling.
    Styler datepicker conditional loading
    classes/controllers/FrmStylesController.php, js/admin/style.js, stubs.php
    PHP: Stops always enqueuing jquery-ui-datepicker; now conditionally depends on it if FrmProAppHelper::use_jquery_datepicker() exists and returns true. JS: initDatepickerSample early-returns when flatpickr is present. Stub: adds FrmProAppHelper::use_jquery_datepicker().
    Whitespace / formatting
    classes/views/frm-fields/back-end/settings.php, js/admin/settings.js
    Removes superfluous blank lines; no functional impact.

    Sequence Diagram(s)

    sequenceDiagram
      participant AdminUI as Admin UI (Form Builder)
      participant JS as formidable_admin.js
      participant Server as FrmFieldsController
      participant View as add_field.php
    
      AdminUI->>JS: frmAdminBuildJS.insertFormField(type, options)
      JS->>Server: POST insert field (type, form_id, field_options)
      Server->>Server: include_new_field(type, form_id, field_options)
      Server->>View: Render field HTML (apply extra attributes filter)
      Server-->>JS: HTML response (data-fid=ID)
      JS->>JS: Parse fieldId, insert HTML, update order/UI
      JS-->>AdminUI: Resolve Promise with element
      JS->>AdminUI: fire frmadmin.afterFieldAddedInFormBuilder({field, fieldId, fieldType, form_id})
    
    Loading

    Estimated code review effort

    🎯 3 (Moderate) | ⏱️ ~20 minutes

    Suggested labels

    action: needs qa

    Suggested reviewers

    • truongwp
    • lauramekaj1
    ✨ Finishing Touches
    • 📝 Generate Docstrings
    🧪 Generate unit tests
    • Create PR with unit tests
    • Post copyable unit tests in a comment
    • Commit unit tests in branch flatpickr/date-time-ranges

    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
    🪧 Tips

    Chat

    There are 3 ways to chat with CodeRabbit:

    • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
      • I pushed a fix in commit <commit_id>, please review it.
      • Explain this complex logic.
      • Open a follow-up GitHub issue for this discussion.
    • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
      • @coderabbitai explain this code block.
    • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
      • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
      • @coderabbitai read src/utils.ts and explain its main purpose.
      • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

    Support

    Need help? Create a ticket on our support page for assistance with any issues or questions.

    CodeRabbit Commands (Invoked using PR comments)

    • @coderabbitai pause to pause the reviews on a PR.
    • @coderabbitai resume to resume the paused reviews.
    • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
    • @coderabbitai full review to do a full review from scratch and review all the files again.
    • @coderabbitai summary to regenerate the summary of the PR.
    • @coderabbitai generate docstrings to generate docstrings for this PR.
    • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
    • @coderabbitai generate unit tests to generate unit tests for this PR.
    • @coderabbitai resolve resolve all the CodeRabbit review comments.
    • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
    • @coderabbitai help to get help.

    Other keywords and placeholders

    • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
    • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
    • Add @coderabbitai anywhere in the PR title to generate the title automatically.

    CodeRabbit Configuration File (.coderabbit.yaml)

    • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
    • Please see the configuration documentation for more information.
    • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

    Documentation and Community

    • Visit our Documentation for detailed information on how to use CodeRabbit.
    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.

    Copy link
    Copy Markdown
    Contributor

    @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: 8

    🧹 Nitpick comments (1)
    classes/controllers/FrmFieldsController.php (1)

    191-200: Update the version placeholder in PHPDoc.

    The filter implementation looks good, but the version placeholder should be updated to reflect the actual version.

    - * @since x.x
    + * @since 6.13

    Regarding the static analysis hint about unused variable: this appears to be a false positive since the variable is used in the view file as mentioned in the summary.

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 98dc219 and 33dac6a.

    📒 Files selected for processing (9)
    • classes/controllers/FrmFieldsController.php (4 hunks)
    • classes/controllers/FrmStylesController.php (1 hunks)
    • classes/models/fields/FrmFieldType.php (1 hunks)
    • classes/views/frm-fields/back-end/settings.php (1 hunks)
    • classes/views/frm-forms/add_field.php (1 hunks)
    • css/admin/frm_admin_global.css (1 hunks)
    • js/admin/settings.js (2 hunks)
    • js/admin/style.js (1 hunks)
    • js/formidable_admin.js (3 hunks)
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    js/formidable_admin.js (1)
    js/admin/legacy-views.js (1)
    • thisFormId (2-2)
    🪛 GitHub Check: Psalm
    classes/controllers/FrmStylesController.php

    [failure] 543-543: InvalidScalarArgument
    classes/controllers/FrmStylesController.php:543:76: InvalidScalarArgument: Argument 2 of is_callable expects bool, but 'use_jquery_datepicker' provided (see https://psalm.dev/012)

    🪛 GitHub Check: PHPStan
    classes/controllers/FrmStylesController.php

    [failure] 543-543:
    Call to an undefined static method FrmProAppHelper::use_jquery_datepicker().

    🪛 GitHub Actions: Psalm Code Analysis
    classes/controllers/FrmStylesController.php

    [error] 543-543: Psalm: InvalidScalarArgument: Argument 2 of is_callable expects bool, but 'use_jquery_datepicker' provided (see https://psalm.dev/012)

    🪛 PHPMD (2.15.0)
    classes/controllers/FrmFieldsController.php

    200-200: Avoid unused local variables such as '$extra_field_attributes'. (Unused Code Rules)

    (UnusedLocalVariable)

    🔇 Additional comments (11)
    classes/views/frm-forms/add_field.php (1)

    6-6: Variable Definition Verified

    I confirmed that $extra_field_attributes is defined in classes/controllers/FrmFieldsController.php via the apply_filters( 'frm_field_container_extra_attributes', … ) call and is in scope when the template classes/views/frm-forms/add_field.php is included. The use of esc_attr() in the template appropriately sanitizes the output. No further action is needed.

    classes/models/fields/FrmFieldType.php (1)

    370-370: range_field setting integrated consistently

    The range_field default was added to default_field_settings() in classes/models/fields/FrmFieldType.php, and your search shows it’s properly checked and defaulted in classes/controllers/FrmFieldsController.php:

    • In FrmFieldsController.php, you guard against an undefined index:
      if ( ! isset( $field['range_field'] ) ) {
          $field['range_field'] = false;
      }
      

    No other references to update. This setting is now fully supported with a safe default and appropriate checks.

    js/admin/settings.js (2)

    17-19: LGTM!

    The event handler integration follows the existing pattern and is properly implemented.


    55-71: Well-implemented function with proper documentation.

    The function has good JSDoc documentation, clear logic, and proper DOM manipulation. The conditional check for 'jquery' library value and appropriate CSS class toggling are well executed.

    js/admin/style.js (1)

    1449-1453: Excellent conflict prevention implementation.

    The conditional check properly prevents jQuery UI datepicker initialization when Pro's flatpickr is available, avoiding potential conflicts. The explanatory comment clearly documents the purpose.

    js/formidable_admin.js (1)

    11064-11065: LGTM! Function exports enable reusability.

    The export of deleteField and insertFormField functions enables their reuse in other plugins as intended by the PR objectives.

    classes/controllers/FrmFieldsController.php (5)

    67-69: LGTM! Proper parameter handling with appropriate sanitization.

    The new field_options parameter is correctly retrieved and sanitized using wp_kses_post, which is appropriate for HTML content that may contain field options.


    73-73: LGTM! Method call updated consistently.

    The method call correctly passes the new field_options parameter to match the updated method signature.


    86-86: LGTM! Method signature and documentation updated correctly.

    The optional $field_options parameter with default empty array maintains backward compatibility while enabling the new functionality.

    Also applies to: 90-90


    93-95: LGTM! Clean merge logic with proper validation.

    The implementation correctly:

    • Checks if field_options is not empty before merging
    • Uses array_merge to combine options, allowing field_options to override defaults
    • Maintains the existing structure

    335-337: LGTM! Good defensive programming practice.

    Adding the range_field safeguard prevents undefined index errors and ensures consistent field structure across all field types.

    Comment thread css/admin/frm_admin_global.css Outdated
    Comment thread classes/views/frm-fields/back-end/settings.php Outdated
    Comment thread classes/controllers/FrmStylesController.php Outdated
    Comment thread js/formidable_admin.js Outdated
    Comment thread js/formidable_admin.js Outdated
    Comment thread js/formidable_admin.js Outdated
    Comment thread js/formidable_admin.js
    Copy link
    Copy Markdown
    Contributor

    @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: 0

    🧹 Nitpick comments (1)
    stubs.php (1)

    82-83: Add documentation and return type annotation for better IDE support.

    The new use_jquery_datepicker() method should include proper documentation and return type annotation to improve code clarity and IDE support.

    Apply this diff to add proper documentation and return type:

    +		/**
    +		 * @return bool
    +		 */
     		public static function use_jquery_datepicker() {
     		}
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 9cd05b0 and 1e35a0b.

    📒 Files selected for processing (4)
    • classes/models/fields/FrmFieldType.php (1 hunks)
    • classes/views/frm-fields/back-end/settings.php (1 hunks)
    • js/formidable_admin.js (5 hunks)
    • stubs.php (1 hunks)
    🚧 Files skipped from review as they are similar to previous changes (3)
    • classes/views/frm-fields/back-end/settings.php
    • classes/models/fields/FrmFieldType.php
    • js/formidable_admin.js

    Comment thread classes/controllers/FrmFieldsController.php Outdated
    Comment thread classes/views/frm-fields/back-end/settings.php Outdated
    Comment thread classes/views/frm-forms/add_field.php Outdated
    Comment thread js/admin/settings.js Outdated
    @Crabcyborg Crabcyborg added this to the 6.23 milestone Aug 7, 2025
    @codecov
    Copy link
    Copy Markdown

    codecov Bot commented Aug 7, 2025

    Codecov Report

    ❌ Patch coverage is 58.33333% with 5 lines in your changes missing coverage. Please review.
    ✅ Project coverage is 27.48%. Comparing base (629b89a) to head (c39b7f1).
    ⚠️ Report is 7 commits behind head on master.

    Files with missing lines Patch % Lines
    classes/controllers/FrmFieldsController.php 60.00% 4 Missing ⚠️
    classes/controllers/FrmStylesController.php 0.00% 1 Missing ⚠️
    Additional details and impacted files
    @@            Coverage Diff            @@
    ##             master    #2428   +/-   ##
    =========================================
      Coverage     27.47%   27.48%           
    - Complexity     8702     8706    +4     
    =========================================
      Files           140      140           
      Lines         28733    28739    +6     
    =========================================
    + Hits           7895     7898    +3     
    - Misses        20838    20841    +3     

    ☔ View full report in Codecov by Sentry.
    📢 Have feedback on the report? Share it here.

    🚀 New features to boost your workflow:
    • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
    • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

    Comment thread js/formidable_admin.js Outdated
    Copy link
    Copy Markdown
    Contributor

    @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: 1

    ♻️ Duplicate comments (6)
    js/formidable_admin.js (6)

    1680-1697: DRY: factor the repeated hook firing into a helper function

    Same hook firing logic appears in 3 places. Create a small helper and reuse it.

    Example helper (place near other utilities):

    function fireAfterFieldAddedHook( msg, fieldType, formId ) {
        const m = msg.match(/data-fid="(\d+)"/);
        if (!m) {
            return;
        }
        wp.hooks.doAction('frmadmin.afterFieldAddedInFormBuilder', {
            field: msg,
            field_id: m[1],
            field_type: fieldType,
            form_id: formId,
        });
    }

    Then here:

    -                if ( fieldId ) {
    -                    /**
    -                     * Fires after a field is added.
    -                     *
    -                     * @since x.x
    -                     *
    -                     * @param {Object} fieldData            The field data.
    -                     * @param {String} fieldData.field      The field HTML.
    -                     * @param {String} fieldData.field_type The field type.
    -                     * @param {String} fieldData.form_id    The form ID.
    -                     */
    -                    wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -                        field: msg,
    -                        fieldId: fieldId,
    -                        fieldType: fieldType,
    -                        form_id: formId,
    -                    }); 
    -                }
    +                fireAfterFieldAddedHook( msg, fieldType, formId );

    2108-2125: Unify hook payload keys with docs (snake_case), and reuse helper

    The payload uses camelCase and duplicates logic again. Align keys and call a helper.

    Minimal key rename if not extracting a helper:

    -                    wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -                        field: msg,
    -                        fieldId: fieldId,
    -                        fieldType: fieldType,
    -                        form_id: formId,
    -                    }); 
    +                    wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +                        field: msg,
    +                        field_id: fieldId,
    +                        field_type: fieldType,
    +                        form_id: formId,
    +                    });

    Or replace the whole block with:

    -                if ( fieldId ) {
    -                    ...
    -                }
    +                fireAfterFieldAddedHook( msg, fieldType, formId );

    2178-2184: Unify hook payload keys; or replace with helper

    Keep payload consistent with JSDoc and other calls. Better: call the shared helper.

    -                            wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -                                field: msg,
    -                                fieldId: fieldId,
    -                                fieldType: fieldType,
    -                                form_id: formId,
    -                            });
    +                            wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +                                field: msg,
    +                                field_id: fieldId,
    +                                field_type: fieldType,
    +                                form_id: formId,
    +                            });

    Or:

    -                            // fire hook here
    -                            ...
    +                            fireAfterFieldAddedHook( msg, fieldType, formId );

    2090-2090: Repeat: guard regex match to prevent runtime error

    Same issue as above; msg.match(...)[1] can be null. Apply the same null-check.

    -                const fieldId     = msg.match( /data-fid="(\d+)"/ )[1];
    +                const fieldIdMatch = msg.match( /data-fid="(\d+)"/ );
    +                const fieldId = fieldIdMatch ? fieldIdMatch[1] : null;

    2157-2158: Repeat: guard regex match to prevent runtime error

    Same null-check issue for fieldId in insertFormField.

    -                    const fieldId      = msg.match( /data-fid="(\d+)"/ )[1];
    +                    const fieldIdMatch = msg.match( /data-fid="(\d+)"/ );
    +                    const fieldId = fieldIdMatch ? fieldIdMatch[1] : null;

    1653-1654: Guard against null regex matches when extracting fieldId

    msg.match(/data-fid="(\d+)"/)[1] will throw if the pattern isn’t present. Store the match result and check it before indexing.

    Apply this diff:

    -                const fieldId   = msg.match( /data-fid="(\d+)"/ )[1];
    +                const fieldIdMatch = msg.match( /data-fid="(\d+)"/ );
    +                const fieldId = fieldIdMatch ? fieldIdMatch[1] : null;
    🧹 Nitpick comments (3)
    js/formidable_admin.js (3)

    1680-1697: Align hook payload keys with docs and WP conventions; JSDoc uses snake_case

    The JSDoc block documents field_type but the payload uses fieldType. Also consider using field_id rather than fieldId for consistency. Update both keys and the JSDoc if needed.

    -                    wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -                        field: msg,
    -                        fieldId: fieldId,
    -                        fieldType: fieldType,
    -                        form_id: formId,
    -                    }); 
    +                    wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +                        field: msg,
    +                        field_id: fieldId,
    +                        field_type: fieldType,
    +                        form_id: formId,
    +                    });

    2146-2154: Use getInsertNewFieldArgs to avoid duplicated request data (per teammate’s note)

    Leverage the existing helper, then add field_options.

    -                data: {
    -                    action: 'frm_insert_field',
    -                    form_id: formId,
    -                    field_type: fieldType,
    -                    field_options: fieldOptions,
    -                    section_id: 0,
    -                    nonce: frmGlobal.nonce,
    -                    has_break: hasBreak,
    -                    last_row_field_ids: getFieldIdsInSubmitRow()
    -                },
    +                data: ( () => {
    +                    const data = getInsertNewFieldArgs( fieldType, 0, formId, hasBreak );
    +                    data.field_options = fieldOptions;
    +                    return data;
    +                } )(),

    2132-2132: Add a brief comment for the setTimeout rationale (why 10ms?)

    If a delay is still needed after resolving, add a one-line comment explaining it (e.g. “allow layout reflow before updating classes”).

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between e5239be and 3c3d37a.

    📒 Files selected for processing (1)
    • js/formidable_admin.js (5 hunks)
    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
    • GitHub Check: Cypress
    • GitHub Check: PHP 8 tests in WP trunk
    • GitHub Check: PHP 7.4 tests in WP trunk
    🔇 Additional comments (1)
    js/formidable_admin.js (1)

    11165-11168: Exports look good and align with PR goal

    Exposing showSaveAndReloadModal, deleteField, and insertFormField matches the PR’s extensibility objectives.

    Comment thread js/formidable_admin.js
    Copy link
    Copy Markdown
    Contributor

    @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: 1

    🔭 Outside diff range comments (1)
    js/formidable_admin.js (1)

    344-347: Fix undefined variable in confirm modal; href/data-href fallback looks good

    • Critical: for (i in dataAtts) runs before dataAtts is assigned. This will throw at runtime. Loop over removeAtts instead.
    • The new href fallback to data-href on the continue button is a sensible enhancement. LGTM.

    Apply this diff:

    -		removeAtts = continueButton.dataset;
    -		for ( i in dataAtts ) {
    -			continueButton.removeAttribute( 'data-' + i );
    -		}
    +		removeAtts = continueButton.dataset;
    +		for ( i in removeAtts ) {
    +			continueButton.removeAttribute( 'data-' + i );
    +		}

    Also applies to: 367-367

    ♻️ Duplicate comments (5)
    classes/views/frm-forms/add_field.php (1)

    6-6: Good fix: render extra attributes via array_to_html_params

    This resolves the earlier escaping/quote issues and outputs well-escaped key="value" pairs.

    js/formidable_admin.js (4)

    1653-1653: Guard regex when extracting fieldId (can throw if no match)

    msg.match(/data-fid="(\d+)"/)[1] will throw if the pattern isn’t found. Guard the match before indexing.

    Apply this diff:

    -				const fieldId   = msg.match( /data-fid="(\d+)"/ )[1];
    +				const fieldIdMatch = msg.match( /data-fid="(\d+)"/ );
    +				const fieldId      = fieldIdMatch ? fieldIdMatch[1] : null;

    1680-1697: DRY: Refactor duplicated hook firing into a helper

    The logic to fire frmadmin.afterFieldAddedInFormBuilder is repeated 3x. Extract it to a single helper to reduce duplication and future maintenance risk. Also benefits centralizing the safe fieldId extraction.

    Add once (near other helpers):

    function fireAfterFieldAddedHook( msg, fieldType, formId ) {
    	const m = msg.match( /data-fid="(\d+)"/ );
    	if ( ! m ) {
    		return;
    	}
    	wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    		field: msg,
    		fieldId: m[1],
    		fieldType,
    		form_id: formId,
    	} );
    }

    Then replace the repeated blocks with:

    -				if ( fieldId ) {
    -					/**
    -					 * Fires after a field is added.
    -					 *
    -					 * @since x.x
    -					 * ...
    -					 */
    -					wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -						field: msg,
    -						fieldId: fieldId,
    -						fieldType: fieldType,
    -						form_id: formId,
    -					});	
    -				}
    +				fireAfterFieldAddedHook( msg, fieldType, formId );

    Also applies to: 2108-2125, 2169-2175


    2091-2091: Guard regex when extracting fieldId in addFieldClick

    Same issue: msg may not contain data-fid, leading to a crash when indexing [1].

    Apply this diff:

    -				const fieldId     = msg.match( /data-fid="(\d+)"/ )[1];
    +				const fieldIdMatch = msg.match( /data-fid="(\d+)"/ );
    +				const fieldId      = fieldIdMatch ? fieldIdMatch[1] : null;

    2132-2181: insertFormField has critical flaws: no DOM insertion, unsafe regex, no Promise reject

    • DOM: afterAddField, updateFieldOrder, and syncLayoutClasses assume the field exists in the builder DOM. This function never appends msg to the DOM, so these calls are unsafe.
    • Regex: unsafe [1] indexing can throw.
    • Promise: no reject path on AJAX errors.

    Rewrite to mirror addFieldClick flow (append to DOM, init DnD, then fire hooks), add a reject path, and guard regex.

    Apply this diff:

    -	function insertFormField( fieldType, fieldOptions = {} ) {
    -
    -		return new Promise( ( resolve ) => {			
    +	function insertFormField( fieldType, fieldOptions = {} ) {
    +		return new Promise( ( resolve, reject ) => {
     			const formId = thisFormId;
     			let hasBreak = 0;
     
     			if ( 'summary' === fieldType ) {
     				hasBreak = $newFields.children( 'li[data-type="break"]' ).length > 0 ? 1 : 0;
     			}
     
     			jQuery.ajax({
     				type: 'POST',
     				url: ajaxurl,
    -				data: Object.assign( getInsertNewFieldArgs( fieldType, 0, formId, hasBreak ), { field_options: fieldOptions } ),
    -				success: function( msg ) {
    -					const fieldElement = jQuery( msg );
    -					const fieldId      = msg.match( /data-fid="(\d+)"/ )[1];
    -
    -					fieldElement[0].style.display = 'none';
    -					resolve( fieldElement );
    -					setTimeout( () => {
    -						updateFieldOrder();
    -						afterAddField( msg, true );
    -						syncLayoutClasses( jQuery( fieldElement.closest( 'ul' ).children()[0] ) );
    -						fieldElement[0].style.display = 'block';
    -
    -						if ( fieldId ) {
    -							/**
    -							 * Fires after a field is added.
    -							 *
    -							 * @since x.x
    -							 *
    -							 * @param {Object} fieldData            The field data.
    -							 * @param {String} fieldData.field      The field HTML.
    -							 * @param {String} fieldData.field_type The field type.
    -							 * @param {String} fieldData.form_id    The form ID.
    -							 */
    -							wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -								field: msg,
    -								fieldId: fieldId,
    -								fieldType: fieldType,
    -								form_id: formId,
    -							});
    -						}
    -					}, 10 );
    -				},
    -				error: handleInsertFieldError
    +				data: ( () => {
    +					const args = getInsertNewFieldArgs( fieldType, 0, formId, hasBreak );
    +					args.field_options = fieldOptions;
    +					return args;
    +				} )(),
    +				success: function( msg ) {
    +					document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' );
    +					const replaceWith = wrapFieldLi( msg );
    +					const submitField = $newFields[0].querySelector( '.edit_field_type_submit' );
    +					if ( submitField ) {
    +						jQuery( submitField.closest( '.frm_field_box:not(.form-field)' ) ).before( replaceWith );
    +					} else {
    +						$newFields.append( replaceWith );
    +					}
    +					// Init DnD on inserted field(s)
    +					replaceWith.each( function() {
    +						makeDroppable( this.querySelector( 'ul.frm_sorting' ) );
    +						makeDraggable( this.querySelector( '.form-field' ), '.frm-move' );
    +					} );
    +					updateFieldOrder();
    +					afterAddField( msg, true );
    +					// Fire after-added hook safely
    +					const m = msg.match( /data-fid="(\d+)"/ );
    +					if ( m ) {
    +						wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +							field: msg,
    +							fieldId: m[1],
    +							fieldType: fieldType,
    +							form_id: formId,
    +						} );
    +					}
    +					resolve( replaceWith );
    +				},
    +				error: function( xhr, status, error ) {
    +					handleInsertFieldError( xhr, status, error );
    +					reject( error || status || 'Insert field failed' );
    +				}
     			});
     		} );
     	}

    Optional follow-ups:

    • Consider honoring shouldStopInsertingField(fieldType) the same way addFieldClick does.
    • Document what the Promise resolves to (the wrapper li.frm_field_box) and update JSDoc.
    🧹 Nitpick comments (6)
    classes/views/frm-forms/add_field.php (1)

    6-6: Optional: restrict attribute keys for stricter safety

    If you want to prevent on* event attributes for users without unfiltered_html, consider filtering keys server-side (eg, allow data-, aria-, role, id, class, style) before rendering. This keeps the API flexible while reducing risk.

    classes/controllers/FrmFieldsController.php (3)

    86-90: Doc nit: clarify $field_options and add @SInCE for new parameter

    Please expand the param description and add an @SInCE tag for the new argument to aid integrators.

    Suggested doc tweak:

    • Describe $field_options as “Optional. Keys to override defaults in field_options at creation.”
    • Add “@SInCE $field_options argument added.”

    93-96: Confirm merge semantics; consider recursive merge for nested options

    array_merge is shallow; nested arrays under field_options will be fully replaced. If you want to preserve nested defaults while overriding only provided leaves, use array_replace_recursive.

    -			$field_values['field_options'] = array_merge( $field_values['field_options'], $field_options );
    +			$field_values['field_options'] = array_replace_recursive( $field_values['field_options'], $field_options );

    If full replacement is intentional, ignore this suggestion.


    191-201: Filter docs and safety: finalize @SInCE and optionally screen unsafe keys

    • Replace @SInCE x.x with the actual release.
    • Optional: For users without unfiltered_html, drop unsafe attribute names (eg on*). This mirrors your existing input attribute gating and reduces accidental XSS vectors in admin.
    -		$extra_field_attributes = apply_filters( 'frm_field_container_extra_attributes', array(), $field, $display );
    +		$extra_field_attributes = apply_filters( 'frm_field_container_extra_attributes', array(), $field, $display );
    +		// For users who cannot post unfiltered HTML, restrict unsafe attribute names.
    +		if ( FrmAppHelper::should_never_allow_unfiltered_html() ) {
    +			$extra_field_attributes = array_filter(
    +				(array) $extra_field_attributes,
    +				function ( $v, $k ) {
    +					return FrmAppHelper::input_key_is_safe( $k );
    +				},
    +				ARRAY_FILTER_USE_BOTH
    +			);
    +		}

    PHPMD “Unused local variable” is a false positive (used in included template). You can suppress it in the method docblock:

    /**
     * @param array|int|object $field_object
     * @param array            $values
     * @param int              $form_id
     * @SuppressWarnings(PHPMD.UnusedLocalVariable) $extra_field_attributes is used in included template (add_field.php).
     */
    js/formidable_admin.js (2)

    1684-1690: Replace @SInCE x.x with the actual version before merge

    These hook docs still show @SInCE x.x. Please set to the release version for traceability.

    Also applies to: 2112-2118, 2162-2168


    11156-11159: New public exports — looks good; add changelog/API notes

    Exporting showSaveAndReloadModal, deleteField, insertFormField, and confirmLinkClick improves extensibility. LGTM. Please:

    • Add these to the public API docs/changelog with @SInCE version.
    • Note insertFormField’s resolved value type in JSDoc.
    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between 3c3d37a and 1568fec.

    📒 Files selected for processing (4)
    • classes/controllers/FrmFieldsController.php (3 hunks)
    • classes/views/frm-fields/back-end/settings.php (1 hunks)
    • classes/views/frm-forms/add_field.php (1 hunks)
    • js/formidable_admin.js (6 hunks)
    🚧 Files skipped from review as they are similar to previous changes (1)
    • classes/views/frm-fields/back-end/settings.php
    🧰 Additional context used
    🧬 Code Graph Analysis (3)
    classes/controllers/FrmFieldsController.php (2)
    classes/helpers/FrmAppHelper.php (2)
    • FrmAppHelper (6-4602)
    • get_post_param (682-692)
    classes/helpers/FrmFieldsHelper.php (1)
    • FrmFieldsHelper (6-2425)
    classes/views/frm-forms/add_field.php (1)
    classes/helpers/FrmAppHelper.php (2)
    • FrmAppHelper (6-4602)
    • array_to_html_params (1366-1375)
    js/formidable_admin.js (2)
    js/admin/legacy-views.js (4)
    • link (274-274)
    • msg (98-98)
    • formId (231-231)
    • thisFormId (2-2)
    js/formidable.js (1)
    • fieldId (1976-1976)
    🪛 PHPMD (2.15.0)
    classes/controllers/FrmFieldsController.php

    200-200: Avoid unused local variables such as '$extra_field_attributes'. (Unused Code Rules)

    (UnusedLocalVariable)

    ⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
    • GitHub Check: PHP 7.4 tests in WP trunk
    • GitHub Check: PHP 8 tests in WP trunk
    • GitHub Check: Cypress
    🔇 Additional comments (1)
    classes/controllers/FrmFieldsController.php (1)

    73-74: LGTM: pass field_options into include_new_field

    Keeps the create flow consistent with the new API.

    Comment thread classes/controllers/FrmFieldsController.php
    Comment thread css/admin/frm_admin_global.css Outdated
    Copy link
    Copy Markdown
    Contributor

    @Crabcyborg Crabcyborg left a comment

    Choose a reason for hiding this comment

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

    Thanks @Liviu-p!

    I removed the jQuery logic from here, and removed a CSS class that I don't think we need.

    I think we can merge this now.

    🚀

    @Crabcyborg Crabcyborg changed the title add filter that will extended the form field container html attribues… add filter that will extended the form field container html attributes… Aug 11, 2025
    Copy link
    Copy Markdown
    Contributor

    @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: 0

    ♻️ Duplicate comments (3)
    js/formidable_admin.js (3)

    2121-2138: DRY the hook firing (duplicate of previous suggestion)

    Same duplication as earlier. Please reuse a helper as suggested above.


    1680-1697: DRY the repeated hook firing into a helper

    This block is duplicated in multiple places. Extract a helper and reuse to avoid drift and improve readability.

    +function fireAfterFieldAddedHook( msg, fieldType, formId ) {
    +	const fieldId = checkMsgForFieldId( msg );
    +	if ( fieldId ) {
    +		wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +			field: msg,
    +			fieldId,
    +			// Keep naming consistent (see note below).
    +			fieldType: fieldType,
    +			form_id: formId,
    +		});
    +	}
    +}

    Then replace this block and the other occurrences with:

    - if ( fieldId ) {
    -   /**
    -    * Fires after a field is added.
    -    * @since x.x
    -    * @param {Object} fieldData            The field data.
    -    * @param {String} fieldData.field      The field HTML.
    -    * @param {String} fieldData.field_type The field type.
    -    * @param {String} fieldData.form_id    The form ID.
    -    */
    -   wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -     field: msg,
    -     fieldId: fieldId,
    -     fieldType: fieldType,
    -     form_id: formId,
    -   });
    - }
    + fireAfterFieldAddedHook( msg, fieldType, formId );

    Note: the documented key is field_type but the emitted key is fieldType. Pick one and keep it consistent across docs and payload. See separate comment.


    2145-2194: Critical: insertFormField never inserts into the DOM but calls DOM-dependent code

    The function resolves with a detached jQuery element and then calls updateFieldOrder(), afterAddField(), and syncLayoutClasses() which assume the field exists in the builder DOM. This can throw (e.g., afterAddField queries document.getElementById on ids that don't exist yet).

    Fix by inserting the returned HTML into the builder (consistent with addFieldClick/drag flow), initializing DnD, then updating state and firing the hook. Also, reject on AJAX error.

    -function insertFormField( fieldType, fieldOptions = {} ) {
    -
    -	return new Promise( ( resolve ) => {			
    +function insertFormField( fieldType, fieldOptions = {} ) {
    +	return new Promise( ( resolve, reject ) => {
     		const formId = thisFormId;
     		let hasBreak = 0;
     
     		if ( 'summary' === fieldType ) {
     			hasBreak = $newFields.children( 'li[data-type="break"]' ).length > 0 ? 1 : 0;
     		}
     
     		jQuery.ajax({
     			type: 'POST',
     			url: ajaxurl,
    -			data: Object.assign( getInsertNewFieldArgs( fieldType, 0, formId, hasBreak ), { field_options: fieldOptions } ),
    +			data: Object.assign( getInsertNewFieldArgs( fieldType, 0, formId, hasBreak ), { field_options: fieldOptions } ),
     			success: function( msg ) {
    -				const fieldElement = jQuery( msg );
    -				const fieldId      = checkMsgForFieldId( msg );
    -
    -				fieldElement[0].style.display = 'none';
    -				resolve( fieldElement );
    -				setTimeout( () => {
    -					updateFieldOrder();
    -					afterAddField( msg, true );
    -					syncLayoutClasses( jQuery( fieldElement.closest( 'ul' ).children()[0] ) );
    -					fieldElement[0].style.display = 'block';
    -
    -					if ( fieldId ) {
    -						/**
    -						 * Fires after a field is added.
    -						 *
    -						 * @since x.x
    -						 *
    -						 * @param {Object} fieldData            The field data.
    -						 * @param {String} fieldData.field      The field HTML.
    -						 * @param {String} fieldData.field_type The field type.
    -						 * @param {String} fieldData.form_id    The form ID.
    -						 */
    -						wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    -							field: msg,
    -							fieldId: fieldId,
    -							fieldType: fieldType,
    -							form_id: formId,
    -						});
    -					}
    -				}, 10 );
    +				document.getElementById( 'frm_form_editor_container' ).classList.add( 'frm-has-fields' );
    +
    +				// Build wrapper consistent with other flows and insert before submit row when present.
    +				const $wrapper = wrapFieldLi( msg );
    +				const submitField = $newFields[0].querySelector( '.edit_field_type_submit' );
    +				if ( submitField ) {
    +					jQuery( submitField.closest( '.frm_field_box:not(.form-field)' ) ).before( $wrapper );
    +				} else {
    +					$newFields.append( $wrapper );
    +				}
    +
    +				// Initialize DnD on the inserted node(s).
    +				$wrapper.each( function() {
    +					makeDroppable( this.querySelector( 'ul.frm_sorting' ) );
    +					makeDraggable( this.querySelector( '.form-field' ), '.frm-move' );
    +				});
    +
    +				// Update builder state and fire events.
    +				updateFieldOrder();
    +				afterAddField( msg, true );
    +				// Fire JS hook in a safe, DRY helper.
    +				if ( typeof fireAfterFieldAddedHook === 'function' ) {
    +					fireAfterFieldAddedHook( msg, fieldType, formId );
    +				} else {
    +					const fieldId = checkMsgForFieldId( msg );
    +					if ( fieldId ) {
    +						wp.hooks.doAction( 'frmadmin.afterFieldAddedInFormBuilder', {
    +							field: msg,
    +							fieldId,
    +							fieldType,
    +							form_id: formId,
    +						});
    +					}
    +				}
    +
    +				resolve( $wrapper );
     			},
    -			error: handleInsertFieldError
    +			error: function( xhr, status, error ) {
    +				handleInsertFieldError( xhr, status, error );
    +				reject( error || status || 'Insert field failed' );
    +			}
     		});
    -	} );
    +	});
     }

    Optional: remove the setTimeout; after insertion, DOM is ready synchronously.

    🧹 Nitpick comments (3)
    js/formidable_admin.js (3)

    367-367: Avoid setting href to "null" when neither href nor data-href is present

    If both attributes are missing, setAttribute will convert null to the string "null". Guard before setting or remove the attribute.

    -continueButton.setAttribute( 'href', link.getAttribute( 'href' ) || link.getAttribute( 'data-href' ) );
    +const href = link.getAttribute( 'href' ) || link.getAttribute( 'data-href' );
    +if ( href ) {
    +	continueButton.setAttribute( 'href', href );
    +} else {
    +	continueButton.removeAttribute( 'href' );
    +}

    1704-1715: Helper LGTM; minor nit: specify radix in parseInt

    The function is solid. Add radix 10 for parseInt.

    - return result ? parseInt( result[1] ) : 0;
    + return result ? parseInt( result[1], 10 ) : 0;

    1684-1691: Align docs with payload and set correct @SInCE

    • The documented property uses field_type but payload uses fieldType. Align either the docs or the payload to a single name.
    • Replace "@SInCE x.x" with the actual version before merge.

    Also applies to: 2126-2133, 2176-2183

    📜 Review details

    Configuration used: CodeRabbit UI
    Review profile: CHILL
    Plan: Pro

    📥 Commits

    Reviewing files that changed from the base of the PR and between f5ecaa2 and 979177f.

    📒 Files selected for processing (2)
    • js/admin/settings.js (0 hunks)
    • js/formidable_admin.js (6 hunks)
    💤 Files with no reviewable changes (1)
    • js/admin/settings.js
    🧰 Additional context used
    🧬 Code Graph Analysis (1)
    js/formidable_admin.js (2)
    js/admin/legacy-views.js (4)
    • link (274-274)
    • msg (98-98)
    • formId (231-231)
    • thisFormId (2-2)
    js/formidable.js (1)
    • fieldId (1976-1976)
    🔇 Additional comments (3)
    js/formidable_admin.js (3)

    1654-1654: Good: safe extraction of fieldId

    Switching to checkMsgForFieldId prevents regex null-dereference errors. LGTM.


    2104-2104: Good: safe fieldId extraction

    Using checkMsgForFieldId here avoids match-null errors. LGTM.


    11169-11173: Public API surface change — confirm intent and stability

    Adding showSaveAndReloadModal, deleteField, insertFormField, confirmLinkClick to the exported object widens the public API. Confirm:

    • Which are intended to be stable for third-party use (docs needed)?
    • Whether exporting deleteField is desired (it performs destructive actions), or should it remain internal.

    @Crabcyborg Crabcyborg merged commit 9886b77 into master Aug 11, 2025
    14 of 15 checks passed
    @Crabcyborg Crabcyborg deleted the flatpickr/date-time-ranges branch August 11, 2025 15:25
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    2 participants