Feat/elementor post form builder#1813
Feat/elementor post form builder#1813sapayth wants to merge 16 commits intoweDevsOfficial:developfrom
Conversation
WalkthroughThis PR introduces a comprehensive Elementor integration for WPUF, adding a new Elementor widget with full styling controls, asset management, and form rendering capabilities. It includes new integration classes, Elementor-specific CSS styling, and updates to field rendering logic to support both admin and frontend contexts within Elementor editors. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
includes/Integrations/Elementor/Elementor.php (2)
55-57: Script enqueued inside a styles method.
wp_enqueue_script( 'wpuf-conditional-logic' )is a script enqueue placed insideenqueue_styles(). Move this toenqueue_scripts()for clarity and correct semantic grouping.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Integrations/Elementor/Elementor.php` around lines 55 - 57, The wp_enqueue_script('wpuf-conditional-logic') call is misplaced inside the enqueue_styles() method; remove that call from enqueue_styles() and add it to the enqueue_scripts() method instead, preserving the wpuf_is_pro_active() conditional check so the script is only enqueued when pro is active; update references in the Elementor class methods enqueue_styles() and enqueue_scripts() accordingly and keep the same script handle 'wpuf-conditional-logic'.
85-95: Unnecessaryfunction_existscheck forwp_enqueue_script.
wp_enqueue_scriptis a core WordPress function and is always available when plugin code runs. This guard is dead code.Simplification
if ( function_exists( 'wp_enqueue_editor' ) ) { wp_enqueue_editor(); - // Also explicitly enqueue TinyMCE scripts if available - if ( function_exists( 'wp_enqueue_script' ) ) { - // Check if these scripts exist and enqueue them - global $wp_scripts; - if ( isset( $wp_scripts->registered['tinymce'] ) ) { - wp_enqueue_script( 'tinymce' ); - } - if ( isset( $wp_scripts->registered['wp-tinymce'] ) ) { - wp_enqueue_script( 'wp-tinymce' ); - } + // Also explicitly enqueue TinyMCE scripts if available + global $wp_scripts; + if ( isset( $wp_scripts->registered['tinymce'] ) ) { + wp_enqueue_script( 'tinymce' ); + } + if ( isset( $wp_scripts->registered['wp-tinymce'] ) ) { + wp_enqueue_script( 'wp-tinymce' ); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Integrations/Elementor/Elementor.php` around lines 85 - 95, Remove the dead guard around wp_enqueue_script: delete the if ( function_exists( 'wp_enqueue_script' ) ) { ... } wrapper and unindent the inner logic so the code always runs; keep the global $wp_scripts declaration and the conditional checks for $wp_scripts->registered['tinymce'] and ['wp-tinymce'] and call wp_enqueue_script('tinymce') and wp_enqueue_script('wp-tinymce') as before (references: wp_enqueue_script, $wp_scripts, 'tinymce', 'wp-tinymce' in the Elementor.php block).includes/Integrations/Elementor/Widget.php (2)
475-509: Duplicated long CSS selector strings for focus states.The focus-state selectors on lines 483, 492, and 500 repeat the same ~300-character selector string verbatim. Extract it to a local variable, similar to
$input_selectoron line 388.Proposed refactor
$this->start_controls_tab( 'tab_input_focus', [ 'label' => __( 'Focus', 'wp-user-frontend' ) ] ); + $input_focus_selector = '{{WRAPPER}} .wpuf-form input[type="text"]:focus, {{WRAPPER}} .wpuf-form input[type="email"]:focus, {{WRAPPER}} .wpuf-form input[type="url"]:focus, {{WRAPPER}} .wpuf-form input[type="password"]:focus, {{WRAPPER}} .wpuf-form input[type="number"]:focus, {{WRAPPER}} .wpuf-form textarea:focus, {{WRAPPER}} .wpuf-form select:focus'; + $this->add_control( 'input_focus_bg_color', [ 'label' => __( 'Background Color', 'wp-user-frontend' ), 'type' => Controls_Manager::COLOR, - 'selectors' => [ - '{{WRAPPER}} .wpuf-form input[type="text"]:focus, ...' => 'background-color: {{VALUE}};', - ], + 'selectors' => [ $input_focus_selector => 'background-color: {{VALUE}};' ], ] );Apply the same variable to the border and box-shadow controls that follow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Integrations/Elementor/Widget.php` around lines 475 - 509, The repeated long focus CSS selector should be extracted to a single local variable (e.g. $input_focus_selector) and reused instead of duplicating the string; update the selectors for the controls identified by 'input_focus_bg_color', the Group_Control_Border with name 'input_focus_border', and the Group_Control_Box_Shadow with name 'input_focus_box_shadow' to use that variable (similar to the existing $input_selector usage) so the identical selector string appears only once.
164-188: Unbounded post query for form dropdown.
posts_per_page => -1fetches all published forms. This is typical for Elementor widget dropdowns and is acceptable for most sites, but on sites with hundreds of forms it could slow down the editor. Consider adding'no_found_rows' => trueand'fields' => 'ids'(then fetching titles) to optimize the query, or capping results with a reasonable limit.Suggested optimization
$forms = get_posts( [ 'post_type' => 'wpuf_forms', 'posts_per_page' => -1, 'post_status' => 'publish', + 'no_found_rows' => true, + 'orderby' => 'title', + 'order' => 'ASC', ] );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@includes/Integrations/Elementor/Widget.php` around lines 164 - 188, The get_wpuf_forms method performs an unbounded get_posts query (posts_per_page => -1) which can be slow on sites with many forms; update get_wpuf_forms to optimize the query by adding 'no_found_rows' => true and 'fields' => 'ids' to fetch only IDs, then retrieve titles with get_post_field or get_the_title for each ID (or enforce a safe cap like 'posts_per_page' => 200) before building the $options array, keeping the apply_filters('wpuf_elementor_form_options', $options, $this) return intact.wpuf.php (1)
196-202: Consider using$_POSTinstead of$_REQUESTfor the AJAX action check.
$_REQUESTincludes cookies and GET parameters in addition to POST data. Since Elementor AJAX calls are POST requests, using$_POST['action']would be more precise and reduce the surface for unexpected matches.Suggested refinement
- $request_act = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; + $request_act = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : '';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wpuf.php` around lines 196 - 202, The AJAX action check uses $_REQUEST which can include GET/cookies; change the $request_act lookup to use $_POST instead (e.g. $request_act = isset($_POST['action']) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : ''), keep the existing DOING_AJAX condition and $is_elementor logic untouched, and leave the branch that instantiates new WeDevs\Wpuf\Frontend() when $is_elementor is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@assets/css/elementor-frontend-forms.css`:
- Around line 85-108: Replace the vendor-prefixed placeholder selectors (e.g.
.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder
input::-webkit-input-placeholder, ...::-ms-input-placeholder) with the standard
::placeholder pseudo-element (e.g.
.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder
input::placeholder and textarea::placeholder), or alternatively wrap this block
with a stylelint-disable comment if you intentionally need prefixes; then
regenerate the compiled CSS from the LESS source so the output is updated.
Ensure you update all occurrences referencing ::-webkit-input-placeholder,
::-moz-placeholder, :-moz-placeholder, ::-ms-input-placeholder to use
::placeholder (or add the disable) and rebuild the asset.
In `@assets/less/elementor-frontend-forms.less`:
- Around line 109-137: The vendor-prefixed placeholder rules under the
.wpuf-elementor-hide-placeholder block cause stylelint errors; replace all
prefixed selectors (::-webkit-input-placeholder, ::-moz-placeholder,
:-moz-placeholder, :-ms-input-placeholder, ::-ms-input-placeholder) with the
standard ::placeholder selector for both input and textarea within the
.wpuf-elementor-hide-placeholder scope (i.e., update the selectors referenced in
the diff to use ::placeholder) and then recompile the LESS to produce updated
CSS.
In `@includes/Integrations.php`:
- Around line 49-51: Fix the PHPCS spacing and multi-line call formatting for
the add_action invocation and wrap the integration instantiation in a try/catch:
change the call to use multi-line arguments with the opening parenthesis last
and the closing parenthesis on its own line, declare the anonymous callback as
"function () { ... }" (note the space), and inside that callback wrap the
"$this->container['elementor'] = new Integrations\Elementor\Elementor();" in try
{ ... } catch (\Throwable $e) { /* log error via
$this->container['logger']->error(...) or similar */ } so errors creating the
Elementor integration are caught and logged for consistency with other
integrations.
In `@includes/Integrations/Elementor/Elementor.php`:
- Around line 116-227: The enqueue_wpuf_form_assets() method duplicates the
script enqueueing and wp_localize_script logic found in
Frontend::enqueue_scripts(); extract the shared logic into a single reusable
helper (for example a static method like FormAssets::enqueue_form_assets() or a
trait method) that encapsulates all wp_enqueue_script/wp_enqueue_style and
wp_localize_script calls (including apply_filters and
wpuf_get_protected_shortcodes usage and the conditional Google Maps block), then
replace the bodies of enqueue_wpuf_form_assets() and Frontend::enqueue_scripts()
to call that helper so both use the same centralized implementation.
- Around line 33-38: PHPCS flagged missing spaces inside the wp_dequeue_style()
calls; update each call to follow WordPress spacing (a single space after the
opening parenthesis and a single space before the closing parenthesis) for the
handles used (e.g., wpuf-frontend-forms, wpuf-layout1, wpuf-layout2,
wpuf-layout3, wpuf-layout4, wpuf-layout5) so each invocation of wp_dequeue_style
has the form wp_dequeue_style( 'handle' ) throughout the Elementor integration
code.
In `@includes/Integrations/Elementor/Widget.php`:
- Around line 1122-1253: The inline script should be moved to an external JS
asset and refactored: replace the global window.onerror override with a targeted
error listener (use window.addEventListener('error', handler, {capture:true})
and scope/filter errors to the .wpuf-elementor-widget-wrapper) instead of
suppressing all errors; remove the fixed 2-second restore and instead
restore/remove the error listener when initialization completes or on a
guaranteed completion event (e.g., after elementorFrontend hook runs) using
elementorFrontend.hooks where available; and change initializeTinyMCE to stop
infinite polling by adding a retry counter/maxAttempts (e.g., maxAttempts and
attempt++ before setTimeout) so it gives up after N tries. Reference
functions/identifiers to change: initializeTinyMCE, window.onerror (replace),
the setTimeout restore block, and elementorFrontend.hooks.addAction handlers.
- Around line 1142-1145: The code uses
document.querySelector('.wpuf-elementor-widget-wrapper') which only selects the
first widget instance; change the DOM scoping so TinyMCE initialization runs
per-widget by querying inside the current widget root instead of the whole
document—e.g. replace the use of document.querySelector with a scoped root like
document.currentScript.parentElement (or the widget root available in this
context) and then call root.querySelectorAll('textarea.wp-editor-area') to find
textareas for that specific widget before initializing TinyMCE (update the
variables wrapper and textareas usage accordingly).
- Around line 1065-1071: In render(), after retrieving $settings via
get_settings_for_display() and before returning or building the shortcode, cast
$form_id to an integer (e.g., $form_id = intval($form_id)) so only numeric IDs
are passed into the shortcode; update the code that uses $form_id in the
shortcode to use this sanitized integer variable (references: render(),
$settings, $form_id, get_settings_for_display()).
- Around line 1116-1117: The wrapper currently calls wp_kses_post() around
$this->get_render_attribute_string('wrapper'), which can strip/corrupt valid
attribute values; remove the wp_kses_post() call and output the
get_render_attribute_string('wrapper') result directly (i.e., use
$this->get_render_attribute_string('wrapper') when building the <div>
attributes) since get_render_attribute_string() already escapes attributes via
esc_attr(), so no additional wp_kses_post() wrapping is needed.
---
Nitpick comments:
In `@includes/Integrations/Elementor/Elementor.php`:
- Around line 55-57: The wp_enqueue_script('wpuf-conditional-logic') call is
misplaced inside the enqueue_styles() method; remove that call from
enqueue_styles() and add it to the enqueue_scripts() method instead, preserving
the wpuf_is_pro_active() conditional check so the script is only enqueued when
pro is active; update references in the Elementor class methods enqueue_styles()
and enqueue_scripts() accordingly and keep the same script handle
'wpuf-conditional-logic'.
- Around line 85-95: Remove the dead guard around wp_enqueue_script: delete the
if ( function_exists( 'wp_enqueue_script' ) ) { ... } wrapper and unindent the
inner logic so the code always runs; keep the global $wp_scripts declaration and
the conditional checks for $wp_scripts->registered['tinymce'] and ['wp-tinymce']
and call wp_enqueue_script('tinymce') and wp_enqueue_script('wp-tinymce') as
before (references: wp_enqueue_script, $wp_scripts, 'tinymce', 'wp-tinymce' in
the Elementor.php block).
In `@includes/Integrations/Elementor/Widget.php`:
- Around line 475-509: The repeated long focus CSS selector should be extracted
to a single local variable (e.g. $input_focus_selector) and reused instead of
duplicating the string; update the selectors for the controls identified by
'input_focus_bg_color', the Group_Control_Border with name 'input_focus_border',
and the Group_Control_Box_Shadow with name 'input_focus_box_shadow' to use that
variable (similar to the existing $input_selector usage) so the identical
selector string appears only once.
- Around line 164-188: The get_wpuf_forms method performs an unbounded get_posts
query (posts_per_page => -1) which can be slow on sites with many forms; update
get_wpuf_forms to optimize the query by adding 'no_found_rows' => true and
'fields' => 'ids' to fetch only IDs, then retrieve titles with get_post_field or
get_the_title for each ID (or enforce a safe cap like 'posts_per_page' => 200)
before building the $options array, keeping the
apply_filters('wpuf_elementor_form_options', $options, $this) return intact.
In `@wpuf.php`:
- Around line 196-202: The AJAX action check uses $_REQUEST which can include
GET/cookies; change the $request_act lookup to use $_POST instead (e.g.
$request_act = isset($_POST['action']) ? sanitize_text_field( wp_unslash(
$_POST['action'] ) ) : ''), keep the existing DOING_AJAX condition and
$is_elementor logic untouched, and leave the branch that instantiates new
WeDevs\Wpuf\Frontend() when $is_elementor is true.
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-webkit-input-placeholder, | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-webkit-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-moz-placeholder, | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-moz-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input:-moz-placeholder, | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea:-moz-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input:-ms-input-placeholder, | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea:-ms-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-ms-input-placeholder, | ||
| .wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-ms-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; |
There was a problem hiding this comment.
Stylelint error: vendor-prefixed placeholder selectors.
selector-no-vendor-prefix will fail on these blocks. Prefer ::placeholder (and let the build add prefixes) or disable the rule for this block. Also regenerate this compiled CSS after fixing the LESS source.
🛠️ Suggested fix (use standard ::placeholder)
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-webkit-input-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-webkit-input-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-moz-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-moz-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input:-moz-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea:-moz-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input:-ms-input-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea:-ms-input-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::-ms-input-placeholder,
-.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::-ms-input-placeholder {
+.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder input::placeholder,
+.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder textarea::placeholder {
opacity: 0;
visibility: hidden;
}🧰 Tools
🪛 Stylelint (17.2.0)
[error] 85-85: Unexpected vendor-prefixed selector "::-webkit-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 86-86: Unexpected vendor-prefixed selector "::-webkit-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 90-90: Unexpected vendor-prefixed selector "::-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 91-91: Unexpected vendor-prefixed selector "::-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 95-95: Unexpected vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 96-96: Unexpected vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 100-100: Unexpected vendor-prefixed selector ":-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 101-101: Unexpected vendor-prefixed selector ":-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 105-105: Unexpected vendor-prefixed selector "::-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 106-106: Unexpected vendor-prefixed selector "::-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@assets/css/elementor-frontend-forms.css` around lines 85 - 108, Replace the
vendor-prefixed placeholder selectors (e.g.
.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder
input::-webkit-input-placeholder, ...::-ms-input-placeholder) with the standard
::placeholder pseudo-element (e.g.
.wpuf-elementor-widget-wrapper.wpuf-elementor-hide-placeholder
input::placeholder and textarea::placeholder), or alternatively wrap this block
with a stylelint-disable comment if you intentionally need prefixes; then
regenerate the compiled CSS from the LESS source so the output is updated.
Ensure you update all occurrences referencing ::-webkit-input-placeholder,
::-moz-placeholder, :-moz-placeholder, ::-ms-input-placeholder to use
::placeholder (or add the disable) and rebuild the asset.
| &.wpuf-elementor-hide-placeholder input::-webkit-input-placeholder, | ||
| &.wpuf-elementor-hide-placeholder textarea::-webkit-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
|
|
||
| &.wpuf-elementor-hide-placeholder input::-moz-placeholder, | ||
| &.wpuf-elementor-hide-placeholder textarea::-moz-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
|
|
||
| &.wpuf-elementor-hide-placeholder input:-moz-placeholder, | ||
| &.wpuf-elementor-hide-placeholder textarea:-moz-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
|
|
||
| &.wpuf-elementor-hide-placeholder input:-ms-input-placeholder, | ||
| &.wpuf-elementor-hide-placeholder textarea:-ms-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } | ||
|
|
||
| &.wpuf-elementor-hide-placeholder input::-ms-input-placeholder, | ||
| &.wpuf-elementor-hide-placeholder textarea::-ms-input-placeholder { | ||
| opacity: 0; | ||
| visibility: hidden; | ||
| } |
There was a problem hiding this comment.
Stylelint error in source LESS for vendor-prefixed selectors.
Replace vendor-prefixed placeholder selectors with ::placeholder and recompile CSS.
🛠️ Suggested fix (LESS source)
- // Hide placeholder option
- &.wpuf-elementor-hide-placeholder input::-webkit-input-placeholder,
- &.wpuf-elementor-hide-placeholder textarea::-webkit-input-placeholder {
- opacity: 0;
- visibility: hidden;
- }
-
- &.wpuf-elementor-hide-placeholder input::-moz-placeholder,
- &.wpuf-elementor-hide-placeholder textarea::-moz-placeholder {
- opacity: 0;
- visibility: hidden;
- }
-
- &.wpuf-elementor-hide-placeholder input:-moz-placeholder,
- &.wpuf-elementor-hide-placeholder textarea:-moz-placeholder {
- opacity: 0;
- visibility: hidden;
- }
-
- &.wpuf-elementor-hide-placeholder input:-ms-input-placeholder,
- &.wpuf-elementor-hide-placeholder textarea:-ms-input-placeholder {
- opacity: 0;
- visibility: hidden;
- }
-
- &.wpuf-elementor-hide-placeholder input::-ms-input-placeholder,
- &.wpuf-elementor-hide-placeholder textarea::-ms-input-placeholder {
- opacity: 0;
- visibility: hidden;
- }
+ // Hide placeholder option
+ &.wpuf-elementor-hide-placeholder {
+ input::placeholder,
+ textarea::placeholder {
+ opacity: 0;
+ visibility: hidden;
+ }
+ }📝 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.
| &.wpuf-elementor-hide-placeholder input::-webkit-input-placeholder, | |
| &.wpuf-elementor-hide-placeholder textarea::-webkit-input-placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| &.wpuf-elementor-hide-placeholder input::-moz-placeholder, | |
| &.wpuf-elementor-hide-placeholder textarea::-moz-placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| &.wpuf-elementor-hide-placeholder input:-moz-placeholder, | |
| &.wpuf-elementor-hide-placeholder textarea:-moz-placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| &.wpuf-elementor-hide-placeholder input:-ms-input-placeholder, | |
| &.wpuf-elementor-hide-placeholder textarea:-ms-input-placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| &.wpuf-elementor-hide-placeholder input::-ms-input-placeholder, | |
| &.wpuf-elementor-hide-placeholder textarea::-ms-input-placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| // Hide placeholder option | |
| &.wpuf-elementor-hide-placeholder { | |
| input::placeholder, | |
| textarea::placeholder { | |
| opacity: 0; | |
| visibility: hidden; | |
| } | |
| } |
🧰 Tools
🪛 Stylelint (17.2.0)
[error] 109-109: Unexpected vendor-prefixed selector "::-webkit-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 110-110: Unexpected vendor-prefixed selector "::-webkit-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 115-115: Unexpected vendor-prefixed selector "::-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 116-116: Unexpected vendor-prefixed selector "::-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 121-121: Unexpected vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 122-122: Unexpected vendor-prefixed selector ":-moz-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 127-127: Unexpected vendor-prefixed selector ":-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 128-128: Unexpected vendor-prefixed selector ":-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 133-133: Unexpected vendor-prefixed selector "::-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
[error] 134-134: Unexpected vendor-prefixed selector "::-ms-input-placeholder" (selector-no-vendor-prefix)
(selector-no-vendor-prefix)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@assets/less/elementor-frontend-forms.less` around lines 109 - 137, The
vendor-prefixed placeholder rules under the .wpuf-elementor-hide-placeholder
block cause stylelint errors; replace all prefixed selectors
(::-webkit-input-placeholder, ::-moz-placeholder, :-moz-placeholder,
:-ms-input-placeholder, ::-ms-input-placeholder) with the standard ::placeholder
selector for both input and textarea within the .wpuf-elementor-hide-placeholder
scope (i.e., update the selectors referenced in the diff to use ::placeholder)
and then recompile the LESS to produce updated CSS.
| add_action('elementor/init', function() { | ||
| $this->container['elementor'] = new Integrations\Elementor\Elementor(); | ||
| }); |
There was a problem hiding this comment.
Fix PHPCS formatting violations and add error handling for consistency.
The PHPCS inspection flags three issues here: missing space after function keyword, opening parenthesis of multi-line call not being last on line, and closing parenthesis not on its own line. Additionally, other integrations in this constructor wrap instantiation in try/catch — this one should too for consistency and resilience.
Proposed fix
- add_action('elementor/init', function() {
- $this->container['elementor'] = new Integrations\Elementor\Elementor();
- });
+ add_action(
+ 'elementor/init', function () {
+ try {
+ $this->container['elementor'] = new Integrations\Elementor\Elementor();
+ } catch ( \Exception $e ) {
+ \WP_User_Frontend::log( 'integration', 'Elementor integration failed' );
+ }
+ }
+ );📝 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.
| add_action('elementor/init', function() { | |
| $this->container['elementor'] = new Integrations\Elementor\Elementor(); | |
| }); | |
| add_action( | |
| 'elementor/init', function () { | |
| try { | |
| $this->container['elementor'] = new Integrations\Elementor\Elementor(); | |
| } catch ( \Exception $e ) { | |
| \WP_User_Frontend::log( 'integration', 'Elementor integration failed' ); | |
| } | |
| } | |
| ); |
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection
[failure] 51-51:
Closing parenthesis of a multi-line function call must be on a line by itself
[failure] 49-49:
Expected 1 space after FUNCTION keyword; 0 found
[failure] 49-49:
Opening parenthesis of a multi-line function call must be the last content on the line
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations.php` around lines 49 - 51, Fix the PHPCS spacing and
multi-line call formatting for the add_action invocation and wrap the
integration instantiation in a try/catch: change the call to use multi-line
arguments with the opening parenthesis last and the closing parenthesis on its
own line, declare the anonymous callback as "function () { ... }" (note the
space), and inside that callback wrap the "$this->container['elementor'] = new
Integrations\Elementor\Elementor();" in try { ... } catch (\Throwable $e) { /*
log error via $this->container['logger']->error(...) or similar */ } so errors
creating the Elementor integration are caught and logged for consistency with
other integrations.
| wp_dequeue_style('wpuf-frontend-forms'); | ||
| wp_dequeue_style('wpuf-layout1'); | ||
| wp_dequeue_style('wpuf-layout2'); | ||
| wp_dequeue_style('wpuf-layout3'); | ||
| wp_dequeue_style('wpuf-layout4'); | ||
| wp_dequeue_style('wpuf-layout5'); |
There was a problem hiding this comment.
Fix PHPCS spacing inside wp_dequeue_style calls.
These lines are flagged by PHPCS for missing spaces after opening parenthesis and before closing parenthesis. The project's coding standard requires WordPress-style spacing.
Proposed fix
- wp_dequeue_style('wpuf-frontend-forms');
- wp_dequeue_style('wpuf-layout1');
- wp_dequeue_style('wpuf-layout2');
- wp_dequeue_style('wpuf-layout3');
- wp_dequeue_style('wpuf-layout4');
- wp_dequeue_style('wpuf-layout5');
+ wp_dequeue_style( 'wpuf-frontend-forms' );
+ wp_dequeue_style( 'wpuf-layout1' );
+ wp_dequeue_style( 'wpuf-layout2' );
+ wp_dequeue_style( 'wpuf-layout3' );
+ wp_dequeue_style( 'wpuf-layout4' );
+ wp_dequeue_style( 'wpuf-layout5' );🧰 Tools
🪛 GitHub Check: Run PHPCS inspection
[failure] 36-36:
Expected 1 spaces after opening parenthesis; 0 found
[failure] 35-35:
Expected 1 spaces before closing parenthesis; 0 found
[failure] 35-35:
Expected 1 spaces after opening parenthesis; 0 found
[failure] 34-34:
Expected 1 spaces before closing parenthesis; 0 found
[failure] 34-34:
Expected 1 spaces after opening parenthesis; 0 found
[failure] 33-33:
Expected 1 spaces before closing parenthesis; 0 found
[failure] 33-33:
Expected 1 spaces after opening parenthesis; 0 found
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Elementor.php` around lines 33 - 38, PHPCS
flagged missing spaces inside the wp_dequeue_style() calls; update each call to
follow WordPress spacing (a single space after the opening parenthesis and a
single space before the closing parenthesis) for the handles used (e.g.,
wpuf-frontend-forms, wpuf-layout1, wpuf-layout2, wpuf-layout3, wpuf-layout4,
wpuf-layout5) so each invocation of wp_dequeue_style has the form
wp_dequeue_style( 'handle' ) throughout the Elementor integration code.
| private function enqueue_wpuf_form_assets() { | ||
| // Core styles (already handled by enqueue_styles, but ensure they're available) | ||
| wp_enqueue_style( 'wpuf-sweetalert2' ); | ||
| wp_enqueue_style( 'wpuf-jquery-ui' ); | ||
|
|
||
| // Core scripts required for all WPUF forms | ||
| wp_enqueue_script( 'suggest' ); | ||
| wp_enqueue_script( 'wpuf-billing-address' ); | ||
| wp_enqueue_script( 'wpuf-upload' ); | ||
| wp_enqueue_script( 'wpuf-frontend-form' ); | ||
| wp_enqueue_script( 'wpuf-sweetalert2' ); | ||
| wp_enqueue_script( 'wpuf-subscriptions' ); | ||
|
|
||
| // Localize wpuf-upload script | ||
| wp_localize_script( | ||
| 'wpuf-upload', | ||
| 'wpuf_upload', | ||
| [ | ||
| 'confirmMsg' => __( 'Are you sure?', 'wp-user-frontend' ), | ||
| 'delete_it' => __( 'Yes, delete it', 'wp-user-frontend' ), | ||
| 'cancel_it' => __( 'No, cancel it', 'wp-user-frontend' ), | ||
| 'ajaxurl' => admin_url( 'admin-ajax.php' ), | ||
| 'nonce' => wp_create_nonce( 'wpuf_nonce' ), | ||
| 'plupload' => [ | ||
| 'url' => admin_url( 'admin-ajax.php' ) . '?nonce=' . wp_create_nonce( 'wpuf-upload-nonce' ), | ||
| 'flash_swf_url' => includes_url( 'js/plupload/plupload.flash.swf' ), | ||
| 'filters' => [ | ||
| [ | ||
| 'title' => __( 'Allowed Files', 'wp-user-frontend' ), | ||
| 'extensions' => '*', | ||
| ], | ||
| ], | ||
| 'multipart' => true, | ||
| 'urlstream_upload' => true, | ||
| 'warning' => __( 'Maximum number of files reached!', 'wp-user-frontend' ), | ||
| 'size_error' => __( 'The file you have uploaded exceeds the file size limit. Please try again.', 'wp-user-frontend' ), | ||
| 'type_error' => __( 'You have uploaded an incorrect file type. Please try again.', 'wp-user-frontend' ), | ||
| ], | ||
| ] | ||
| ); | ||
|
|
||
| // Localize wpuf-frontend-form script | ||
| wp_localize_script( | ||
| 'wpuf-frontend-form', | ||
| 'wpuf_frontend', | ||
| apply_filters( | ||
| 'wpuf_frontend_object', | ||
| [ | ||
| 'asset_url' => WPUF_ASSET_URI, | ||
| 'ajaxurl' => admin_url( 'admin-ajax.php' ), | ||
| 'error_message' => __( 'Please fix the errors to proceed', 'wp-user-frontend' ), | ||
| 'nonce' => wp_create_nonce( 'wpuf_nonce' ), | ||
| 'word_limit' => __( 'Word limit reached', 'wp-user-frontend' ), | ||
| 'cancelSubMsg' => __( 'Are you sure you want to cancel your current subscription ?', 'wp-user-frontend' ), | ||
| 'delete_it' => __( 'Yes', 'wp-user-frontend' ), | ||
| 'cancel_it' => __( 'No', 'wp-user-frontend' ), | ||
| 'word_max_title' => __( 'Maximum word limit reached. Please shorten your texts.', 'wp-user-frontend' ), | ||
| 'word_max_details' => __( 'This field supports a maximum of %number% words, and the limit is reached. Remove a few words to reach the acceptable limit of the field.', 'wp-user-frontend' ), | ||
| 'word_min_title' => __( 'Minimum word required.', 'wp-user-frontend' ), | ||
| 'word_min_details' => __( 'This field requires minimum %number% words. Please add some more text.', 'wp-user-frontend' ), | ||
| 'char_max_title' => __( 'Maximum character limit reached. Please shorten your texts.', 'wp-user-frontend' ), | ||
| 'char_max_details' => __( 'This field supports a maximum of %number% characters, and the limit is reached. Remove a few characters to reach the acceptable limit of the field.', 'wp-user-frontend' ), | ||
| 'char_min_title' => __( 'Minimum character required.', 'wp-user-frontend' ), | ||
| 'char_min_details' => __( 'This field requires minimum %number% characters. Please add some more character.', 'wp-user-frontend' ), | ||
| 'protected_shortcodes' => wpuf_get_protected_shortcodes(), | ||
| 'protected_shortcodes_message' => __( 'Using %shortcode% is restricted', 'wp-user-frontend' ), | ||
| 'password_warning_weak' => __( 'Your password should be at least weak in strength', 'wp-user-frontend' ), | ||
| 'password_warning_medium' => __( 'Your password needs to be medium strength for better protection', 'wp-user-frontend' ), | ||
| 'password_warning_strong' => __( 'Create a strong password for maximum security', 'wp-user-frontend' ), | ||
| ] | ||
| ) | ||
| ); | ||
|
|
||
| wp_localize_script( | ||
| 'wpuf-frontend-form', | ||
| 'error_str_obj', | ||
| [ | ||
| 'required' => __( 'is required', 'wp-user-frontend' ), | ||
| 'mismatch' => __( 'does not match', 'wp-user-frontend' ), | ||
| 'validation' => __( 'is not valid', 'wp-user-frontend' ), | ||
| ] | ||
| ); | ||
|
|
||
| // Localize subscription script | ||
| wp_localize_script( | ||
| 'wpuf-subscriptions', | ||
| 'wpuf_subscription', | ||
| apply_filters( | ||
| 'wpuf_subscription_js_data', | ||
| [ | ||
| 'pack_notice' => __( 'Please Cancel Your Currently Active Pack first!', 'wp-user-frontend' ), | ||
| ] | ||
| ) | ||
| ); | ||
|
|
||
| // Localize billing address script | ||
| wp_localize_script( | ||
| 'wpuf-billing-address', | ||
| 'ajax_object', | ||
| [ | ||
| 'ajaxurl' => admin_url( 'admin-ajax.php' ), | ||
| 'fill_notice' => __( 'Some Required Fields are not filled!', 'wp-user-frontend' ), | ||
| ] | ||
| ); | ||
|
|
||
| // Conditionally enqueue Google Maps if API key is configured | ||
| $api_key = wpuf_get_option( 'gmap_api_key', 'wpuf_general' ); | ||
| if ( ! empty( $api_key ) ) { | ||
| $scheme = is_ssl() ? 'https' : 'http'; | ||
| wp_enqueue_script( 'wpuf-google-maps', $scheme . '://maps.google.com/maps/api/js?libraries=places&key=' . $api_key, [], null, true ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Significant code duplication with Frontend::enqueue_scripts().
enqueue_wpuf_form_assets() duplicates ~110 lines of script enqueuing and wp_localize_script calls that already exist in includes/Frontend.php (lines 42–175 of the relevant snippet). If any localized key or script handle changes in one place, the other will silently drift.
Consider extracting the shared localization/enqueue logic into a reusable helper (e.g., a static method or a trait) that both Frontend::enqueue_scripts() and this Elementor integration can call. This would eliminate the duplication and ensure changes propagate consistently.
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection
[warning] 225-225:
Resource version not set in call to wp_enqueue_script(). This means new versions of the script may not always be loaded due to browser caching.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Elementor.php` around lines 116 - 227, The
enqueue_wpuf_form_assets() method duplicates the script enqueueing and
wp_localize_script logic found in Frontend::enqueue_scripts(); extract the
shared logic into a single reusable helper (for example a static method like
FormAssets::enqueue_form_assets() or a trait method) that encapsulates all
wp_enqueue_script/wp_enqueue_style and wp_localize_script calls (including
apply_filters and wpuf_get_protected_shortcodes usage and the conditional Google
Maps block), then replace the bodies of enqueue_wpuf_form_assets() and
Frontend::enqueue_scripts() to call that helper so both use the same centralized
implementation.
| protected function render() { | ||
| $settings = $this->get_settings_for_display(); | ||
| $form_id = isset( $settings['form_id'] ) ? $settings['form_id'] : null; | ||
|
|
||
| if ( empty( $form_id ) ) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Cast form_id to integer for defense in depth.
$form_id is passed directly into the shortcode string. While Elementor sanitizes its settings, an explicit intval() ensures only valid numeric IDs reach the shortcode, preventing any edge-case injection.
Proposed fix
$settings = $this->get_settings_for_display();
- $form_id = isset( $settings['form_id'] ) ? $settings['form_id'] : null;
+ $form_id = isset( $settings['form_id'] ) ? intval( $settings['form_id'] ) : 0;
- if ( empty( $form_id ) ) {
+ if ( ! $form_id ) {
return;
}📝 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.
| protected function render() { | |
| $settings = $this->get_settings_for_display(); | |
| $form_id = isset( $settings['form_id'] ) ? $settings['form_id'] : null; | |
| if ( empty( $form_id ) ) { | |
| return; | |
| } | |
| protected function render() { | |
| $settings = $this->get_settings_for_display(); | |
| $form_id = isset( $settings['form_id'] ) ? intval( $settings['form_id'] ) : 0; | |
| if ( ! $form_id ) { | |
| return; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Widget.php` around lines 1065 - 1071, In
render(), after retrieving $settings via get_settings_for_display() and before
returning or building the shortcode, cast $form_id to an integer (e.g., $form_id
= intval($form_id)) so only numeric IDs are passed into the shortcode; update
the code that uses $form_id in the shortcode to use this sanitized integer
variable (references: render(), $settings, $form_id,
get_settings_for_display()).
| echo '<div ' . wp_kses_post( $this->get_render_attribute_string( 'wrapper' ) ) . '>'; | ||
| echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- shortcode output |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the actual file and see the code at those lines
cat -n includes/Integrations/Elementor/Widget.php | sed -n '1110,1125p'Repository: weDevsOfficial/wp-user-frontend
Length of output: 1031
🏁 Script executed:
# Let's also search for the get_render_attribute_string method definition
# This is likely inherited from Elementor's base widget class
rg "get_render_attribute_string" -A 15 -B 2Repository: weDevsOfficial/wp-user-frontend
Length of output: 1781
🏁 Script executed:
# Let's understand what wp_kses_post does - search for its documentation or usage
rg "wp_kses_post" -B 2 -A 5 | head -100Repository: weDevsOfficial/wp-user-frontend
Length of output: 4988
🏁 Script executed:
# Find the class definition and parent class in Widget.php
head -100 includes/Integrations/Elementor/Widget.php | grep -E "^class|extends"Repository: weDevsOfficial/wp-user-frontend
Length of output: 109
🏁 Script executed:
# Search for any documentation or references about what get_render_attribute_string returns
rg "get_render_attribute_string" -B 5 -A 10Repository: weDevsOfficial/wp-user-frontend
Length of output: 1665
🌐 Web query:
Elementor get_render_attribute_string escaping esc_attr
💡 Result:
get_render_attribute_string() already escapes attribute values with esc_attr().
In Elementor core, Controls_Stack::get_render_attribute_string() returns Utils::render_html_attributes( $this->render_attributes[ $element ] ), and Utils::render_html_attributes() builds each attribute as key="esc_attr(value)". So you should pass raw values (or sanitized/validated values) into add_render_attribute() and not wrap them in esc_attr() again. [1], [2]
Notes:
- For URL attributes like
href/src, useesc_url()when you set the value, becauseesc_attr()isn’t URL validation/escaping. [3] - Elementor’s own docs still recommend “escape user data”, but when you output via
get_render_attribute_string(), the escaping for attribute context is handled by Elementor. [4]
Sources
- Elementor
Controls_Stack::get_render_attribute_string()implementation. (raw.githubusercontent.com) - Elementor
Utils::render_html_attributes()usesesc_attr()on values. (raw.githubusercontent.com) - WordPress
esc_attr()notes about usingesc_url()for URI attributes. (developer.wordpress.org) - Elementor developer docs on render attributes. (developers.elementor.com)
Citations:
- 1: https://raw.githubusercontent.com/elementor/elementor/master/includes/base/controls-stack.php
- 2: https://raw.githubusercontent.com/elementor/elementor/master/includes/utils.php
- 3: https://developer.wordpress.org/reference/functions/esc_attr/?utm_source=openai
- 4: https://developers.elementor.com/docs/widgets/rendering-html-attribute/index.html?utm_source=openai
Remove wp_kses_post() wrapper from get_render_attribute_string() output.
Elementor's get_render_attribute_string() already escapes attribute values via esc_attr() internally. Wrapping it with wp_kses_post() is incorrect because wp_kses_post() is designed for post content and can strip or corrupt HTML5 attributes like data-* attributes and event handlers.
Proposed fix
- echo '<div ' . wp_kses_post( $this->get_render_attribute_string( 'wrapper' ) ) . '>';
+ echo '<div ' . $this->get_render_attribute_string( 'wrapper' ) . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Elementor's render attribute string is pre-escaped🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Widget.php` around lines 1116 - 1117, The
wrapper currently calls wp_kses_post() around
$this->get_render_attribute_string('wrapper'), which can strip/corrupt valid
attribute values; remove the wp_kses_post() call and output the
get_render_attribute_string('wrapper') result directly (i.e., use
$this->get_render_attribute_string('wrapper') when building the <div>
attributes) since get_render_attribute_string() already escapes attributes via
esc_attr(), so no additional wp_kses_post() wrapping is needed.
| <script> | ||
| (function() { | ||
| // Suppress non-critical errors for fields that require external scripts | ||
| // (Google Maps, barrating) that may not be loaded in Elementor preview | ||
| var originalErrorHandler = window.onerror; | ||
| window.onerror = function(msg, url, line, col, error) { | ||
| // Suppress Google Maps and barrating errors in Elementor preview | ||
| if (msg && ( | ||
| msg.indexOf('google is not defined') !== -1 || | ||
| msg.indexOf('barrating is not a function') !== -1 || | ||
| msg.indexOf('$(...).barrating is not a function') !== -1 | ||
| )) { | ||
| return true; // Suppress error | ||
| } | ||
| // Call original error handler for other errors | ||
| if (originalErrorHandler) { | ||
| return originalErrorHandler.apply(this, arguments); | ||
| } | ||
| return false; | ||
| }; | ||
|
|
||
| var wrapper = document.querySelector('.wpuf-elementor-widget-wrapper'); | ||
| if (wrapper) { | ||
| var textareas = wrapper.querySelectorAll('textarea.wp-editor-area'); | ||
|
|
||
| // Function to initialize TinyMCE for a textarea | ||
| function initializeTinyMCE(textarea) { | ||
| var editorId = textarea.id; | ||
|
|
||
| // Wait for wp.editor to be available | ||
| if (typeof wp === 'undefined' || typeof wp.editor === 'undefined') { | ||
| setTimeout(function() { | ||
| initializeTinyMCE(textarea); | ||
| }, 100); | ||
| return false; | ||
| } | ||
|
|
||
| if (typeof wp.editor.initialize !== 'function') { | ||
| return false; | ||
| } | ||
|
|
||
| // Check if already initialized | ||
| if (typeof tinymce !== 'undefined' && tinymce.get(editorId)) { | ||
| return true; | ||
| } | ||
|
|
||
| // Get editor settings - try to match WordPress default settings | ||
| // Check if there's a wp-editor-wrap parent to determine settings | ||
| var wrap = textarea.closest('.wp-editor-wrap'); | ||
| var isTeeny = wrap && wrap.classList.contains('html-active') ? false : (wrap && wrap.classList.contains('tmce-active') ? false : true); | ||
|
|
||
| var editorSettings = { | ||
| tinymce: { | ||
| wpautop: true, | ||
| toolbar1: 'bold,italic,bullist,numlist,link', | ||
| toolbar2: '', | ||
| media_buttons: false, | ||
| resize: true | ||
| }, | ||
| quicktags: false, | ||
| textarea_name: textarea.name || editorId | ||
| }; | ||
|
|
||
| // If teeny mode, use simpler toolbar | ||
| if (isTeeny || textarea.closest('.wpuf-fields').querySelector('.mce-tinymce')) { | ||
| editorSettings.tinymce.toolbar1 = 'bold,italic,bullist,numlist'; | ||
| editorSettings.teeny = true; | ||
| } | ||
|
|
||
| try { | ||
| wp.editor.initialize(editorId, editorSettings); | ||
| return true; | ||
| } catch(e) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| // Check existing instances and initialize if needed | ||
| textareas.forEach(function(textarea) { | ||
| var editorId = textarea.id; | ||
| if (typeof tinymce === 'undefined' || !tinymce.get(editorId)) { | ||
| initializeTinyMCE(textarea); | ||
| } | ||
| }); | ||
|
|
||
| // Check after a delay to see if TinyMCE initializes or retry if needed | ||
| setTimeout(function() { | ||
| textareas.forEach(function(textarea) { | ||
| var editorId = textarea.id; | ||
| if (typeof tinymce === 'undefined' || !tinymce.get(editorId)) { | ||
| initializeTinyMCE(textarea); | ||
| } | ||
| }); | ||
| }, 500); | ||
|
|
||
| // Also check when Elementor triggers content refresh | ||
| if (typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks) { | ||
| elementorFrontend.hooks.addAction('frontend/element_ready/wpuf-form.default', function($scope) { | ||
| setTimeout(function() { | ||
| var textareas = $scope.find('textarea.wp-editor-area'); | ||
| textareas.each(function(idx, textarea) { | ||
| var editorId = textarea.id; | ||
| if (typeof tinymce === 'undefined' || !tinymce.get(editorId)) { | ||
| if (typeof wp !== 'undefined' && typeof wp.editor !== 'undefined' && typeof wp.editor.initialize === 'function') { | ||
| try { | ||
| wp.editor.initialize(editorId, { | ||
| tinymce: { | ||
| wpautop: true, | ||
| toolbar1: 'bold,italic,bullist,numlist,link', | ||
| toolbar2: '', | ||
| media_buttons: false | ||
| }, | ||
| quicktags: false | ||
| }); | ||
| } catch(e) { | ||
| // Silent fail | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| }, 100); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| // Restore original error handler after initialization | ||
| setTimeout(function() { | ||
| if (originalErrorHandler) { | ||
| window.onerror = originalErrorHandler; | ||
| } | ||
| }, 2000); | ||
| })(); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Large inline JS block — consider extracting to a separate asset file.
This ~130-line inline script handles TinyMCE initialization, global error handler overriding, and Elementor hook integration. A few specific concerns beyond the extraction suggestion:
-
Global
window.onerroroverride (lines 1126–1141): Suppressing errors globally for up to 2 seconds can mask legitimate bugs from other scripts running on the page. Consider usingaddEventListener('error', ...)with{ once: true }or a more targeted approach. -
Arbitrary 2-second restore timeout (lines 1248–1252): There's no guarantee initialization completes within 2 seconds, and no guarantee errors from unrelated scripts won't be swallowed during this window.
-
Recursive
setTimeoutretry with no max attempts (lines 1152–1156):initializeTinyMCEcalls itself viasetTimeoutindefinitely ifwp.editornever becomes available. Add a retry counter to avoid an infinite polling loop.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Widget.php` around lines 1122 - 1253, The
inline script should be moved to an external JS asset and refactored: replace
the global window.onerror override with a targeted error listener (use
window.addEventListener('error', handler, {capture:true}) and scope/filter
errors to the .wpuf-elementor-widget-wrapper) instead of suppressing all errors;
remove the fixed 2-second restore and instead restore/remove the error listener
when initialization completes or on a guaranteed completion event (e.g., after
elementorFrontend hook runs) using elementorFrontend.hooks where available; and
change initializeTinyMCE to stop infinite polling by adding a retry
counter/maxAttempts (e.g., maxAttempts and attempt++ before setTimeout) so it
gives up after N tries. Reference functions/identifiers to change:
initializeTinyMCE, window.onerror (replace), the setTimeout restore block, and
elementorFrontend.hooks.addAction handlers.
|
|
||
| var wrapper = document.querySelector('.wpuf-elementor-widget-wrapper'); | ||
| if (wrapper) { | ||
| var textareas = wrapper.querySelectorAll('textarea.wp-editor-area'); |
There was a problem hiding this comment.
querySelector selects only the first widget on the page — multi-widget bug.
document.querySelector('.wpuf-elementor-widget-wrapper') targets the first matching element in the DOM. If multiple WPUF form widgets are placed on the same page, TinyMCE initialization only runs for the first widget's textareas.
Scope the query to the current widget's rendered output instead, for example by using document.currentScript.parentElement:
Proposed fix
- var wrapper = document.querySelector('.wpuf-elementor-widget-wrapper');
+ // Scope to the current widget instance — currentScript is the inline <script> tag
+ var wrapper = document.currentScript.closest('.wpuf-elementor-widget-wrapper')
+ || document.currentScript.parentElement;📝 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.
| var wrapper = document.querySelector('.wpuf-elementor-widget-wrapper'); | |
| if (wrapper) { | |
| var textareas = wrapper.querySelectorAll('textarea.wp-editor-area'); | |
| // Scope to the current widget instance — currentScript is the inline <script> tag | |
| var wrapper = document.currentScript.closest('.wpuf-elementor-widget-wrapper') | |
| || document.currentScript.parentElement; | |
| if (wrapper) { | |
| var textareas = wrapper.querySelectorAll('textarea.wp-editor-area'); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Integrations/Elementor/Widget.php` around lines 1142 - 1145, The
code uses document.querySelector('.wpuf-elementor-widget-wrapper') which only
selects the first widget instance; change the DOM scoping so TinyMCE
initialization runs per-widget by querying inside the current widget root
instead of the whole document—e.g. replace the use of document.querySelector
with a scoped root like document.currentScript.parentElement (or the widget root
available in this context) and then call
root.querySelectorAll('textarea.wp-editor-area') to find textareas for that
specific widget before initializing TinyMCE (update the variables wrapper and
textareas usage accordingly).
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@includes/Integrations/Elementor/Elementor.php`:
- Around line 32-38: Update the spacing of all wp_dequeue_style() calls to
follow WordPress coding standards by adding a single space after the opening
parenthesis and before the closing parenthesis; modify the six calls to
wp_dequeue_style in the Elementor class (or file) so each invocation uses the
proper spacing around their string argument (e.g., wp_dequeue_style(
'wpuf-frontend-forms' )).
- Around line 117-210: The method enqueue_wpuf_form_assets duplicates the
script/style enqueue and localization arrays already implemented in
Frontend::enqueue_scripts(); extract the shared enqueue and localization logic
into a single helper (e.g., a protected/private method like
register_wpuf_frontend_assets or a shared trait/class) and have both
enqueue_wpuf_form_assets and Frontend::enqueue_scripts call that helper;
preserve exact wp_enqueue_script/style handles ('wpuf-upload',
'wpuf-frontend-form', 'wpuf-sweetalert2', 'wpuf-subscriptions', etc.), all
wp_localize_script keys ('wpuf_upload', 'wpuf_frontend', 'error_str_obj',
'wpuf_subscription') and apply_filters usage so behavior and filters remain
identical while eliminating duplication.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
wpuf.php (1)
205-208: Suppress PHPCS false positives and prefer$_POSTfor the AJAX action check.Lines 205–206 are flagged twice each by PHPCS ("Processing form data without nonce verification"). These are genuine false positives — the values are only compared for routing/detection and are never persisted, rendered, or used in a query — but the CI check will keep reporting them until they're suppressed.
Additionally, line 206 uses
$_REQUEST, which in some PHP configurations also merges$_COOKIE(controlled by therequest_orderINI). For WordPress AJAX (POST toadmin-ajax.php) the more precise and conventional superglobal is$_POST.♻️ Proposed fix
- $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; - $request_act = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- action param used only for Elementor context detection, not form processing. + $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- action param used only for Elementor context detection, not form processing. + $request_act = isset( $_POST['action'] ) ? sanitize_text_field( wp_unslash( $_POST['action'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@wpuf.php` around lines 205 - 208, Replace the use of $_REQUEST with $_POST for determining the AJAX action and add inline PHPCS suppression comments to silence the nonce-verification false positives: update the $request_act assignment to read from $_POST instead of $_REQUEST (keep sanitize_text_field( wp_unslash(...) )) and add a single-line PHPCS ignore (e.g. phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification) to the $get_action and $request_act assignment lines so CI stops flagging them while leaving $is_elementor logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@wpuf.php`:
- Around line 205-208: Replace the use of $_REQUEST with $_POST for determining
the AJAX action and add inline PHPCS suppression comments to silence the
nonce-verification false positives: update the $request_act assignment to read
from $_POST instead of $_REQUEST (keep sanitize_text_field( wp_unslash(...) ))
and add a single-line PHPCS ignore (e.g. phpcs:ignore
WordPress.Security.NonceVerification.NoNonceVerification) to the $get_action and
$request_act assignment lines so CI stops flagging them while leaving
$is_elementor logic unchanged.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@includes/Fields/Field_Contract.php`:
- Line 772: Replace the placeholder `@since` token in the docblock inside
Field_Contract.php (the docblock attached to the Field_Contract class/method)
with the actual release version string used by this project (e.g., match other
`@since` annotations in the repo) so it no longer reads `@since` WPUF_SINCE but the
real version number for this release.
In `@wpuf.php`:
- Around line 205-208: The code reads action ambiguously from $_REQUEST and uses
sanitize_text_field; change the detection to explicitly read $_GET['action']
into $get_action and $_POST['action'] into a new $post_action (instead of
$request_act), and use sanitize_key() when normalizing both action values;
update the $is_elementor condition to check $get_action === 'elementor' || (
defined('DOING_AJAX') && DOING_AJAX && $post_action === 'elementor_ajax' ) to
avoid $_REQUEST and use slug-safe sanitization.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 786245c5-730c-4cf5-8df5-f583b7b9b4ed
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (3)
includes/Fields/Field_Contract.phpwpuf-functions.phpwpuf.php
🚧 Files skipped from review as they are similar to previous changes (1)
- wpuf-functions.php
| * Returns true for admin dashboard, but false for Elementor editor | ||
| * since Elementor editor needs frontend-style markup for proper preview. | ||
| * | ||
| * @since WPUF_SINCE |
There was a problem hiding this comment.
Replace placeholder @since WPUF_SINCE with a real version.
Line 772 still contains a placeholder token. This should be an actual release version before merge.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@includes/Fields/Field_Contract.php` at line 772, Replace the placeholder
`@since` token in the docblock inside Field_Contract.php (the docblock attached to
the Field_Contract class/method) with the actual release version string used by
this project (e.g., match other `@since` annotations in the repo) so it no longer
reads `@since` WPUF_SINCE but the real version number for this release.
| $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; | ||
| $request_act = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; | ||
| $is_elementor = ( $get_action === 'elementor' ) | ||
| || ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && $request_act === 'elementor_ajax' ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "wpuf.php" -type fRepository: weDevsOfficial/wp-user-frontend
Length of output: 85
🏁 Script executed:
# Get context around lines 205-208 in wpuf.php
find . -name "wpuf.php" -type f -exec head -220 {} \; | tail -30Repository: weDevsOfficial/wp-user-frontend
Length of output: 1828
🌐 Web query:
PHP $_REQUEST vs $_POST $_GET WordPress AJAX action detection best practices
💡 Result:
What WordPress does (and why you see $_REQUEST['action'])
WordPress’ admin-ajax.php routes requests by reading the action parameter and firing dynamic hooks like wp_ajax_{$action} (logged-in) and wp_ajax_nopriv_{$action} (logged-out). In core docs, the dynamic part is explicitly $_REQUEST['action']. [1][2]
$_REQUEST vs $_POST/$_GET (PHP best practice)
In PHP, $_REQUEST is a merge of $_GET, $_POST, and $_COOKIE, and the precedence depends on request_order/variables_order, so it’s ambiguous and can be influenced by cookies/URL params. Prefer explicit $_POST or $_GET for predictability and security. [3]
Best-practice pattern for WordPress AJAX
-
Don’t “detect the action” inside the handler by reading
$_REQUEST['action'].
Route by hooks instead:add_action('wp_ajax_my_action', 'my_handler');add_action('wp_ajax_nopriv_my_action', 'my_handler');(if needed) [2][1]
-
Choose one transport for your data (usually POST) and read from it explicitly:
- Use
$_POST[...]for payload fields (and optionally requirePOSTvia$_SERVER['REQUEST_METHOD']). - Avoid
$_REQUESTfor anything security-relevant. [3]
- Use
-
Nonce + capability checks
- Verify the request with
check_ajax_referer( 'my_action', 'security' )(or default nonce keys). [4] - Also enforce authorization with
current_user_can()for privileged operations (nonces are not authorization). [4]
- Verify the request with
-
Sanitize early + unslash
WordPress input is slashed; typical pattern is:
sanitize_text_field( wp_unslash( $_POST['field'] ?? '' ) ). [5] -
Use WP response helpers
Preferwp_send_json_success()/wp_send_json_error()(thenwp_die()), rather than echoing raw strings (general WP AJAX convention). [4]
Example skeleton
add_action('wp_ajax_my_action', 'my_action_handler');
add_action('wp_ajax_nopriv_my_action', 'my_action_handler'); // if public
function my_action_handler() {
if ( ! wp_doing_ajax() ) { wp_die('Not AJAX', 400); } // optional [6]
check_ajax_referer( 'my_action', 'security' ); // expects nonce in $_REQUEST by design [4]
if ( $_SERVER['REQUEST_METHOD'] !== 'POST' ) { wp_die('Method', 405); }
$title = sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) ); // [5]
wp_send_json_success([ 'title' => $title ]);
}Bottom line: it’s fine that WordPress core uses $_REQUEST['action'] for routing, but in your handlers you should generally be explicit ($_POST/$_GET), validate/nonce-check, and sanitize/unslash. [1][2][3][4][5]
Sources: [1] [2] [3] [4] [5] [6]
🌐 Web query:
WordPress wp_ajax request action parameter sanitization sanitize_key vs sanitize_text_field
💡 Result:
For the admin-ajax.php action parameter (the thing that maps to wp_ajax_{action} / wp_ajax_nopriv_{action}), treat it as an internal identifier / key, not free text—so sanitize_key() is the right sanitizer if you ever need to sanitize/normalize it yourself. It lowercases and strips everything except a-z0-9_-, which matches how “action names” are typically structured. [1]
By contrast, sanitize_text_field() is for human-entered text: it strips tags, trims, collapses whitespace, removes tabs/newlines, etc., but it does not enforce a strict “identifier character set” the way sanitize_key() does. That makes it a poorer fit for hook/action names and other slug-like identifiers. [2]
Practical guidance:
- Normally you don’t need to read/sanitize
$_POST['action']at all inside your callback—WordPress routes the request based on it before your function runs, and core also started requiring a valid (scalar)actionvalue to be set foradmin-ajax.phprequests (to avoid notices/invalid input). [3] - Do sanitize your other request parameters based on what they represent (IDs →
absint(), flags/keys →sanitize_key(), short text →sanitize_text_field(), HTML →wp_kses_post(), etc.). - Don’t confuse sanitizing
actionwith securing the endpoint: still usecheck_ajax_referer()and capability checks as appropriate.
Sources
[1] sanitize_key() reference (allowed chars / behavior) (developer.wordpress.org)
[2] WordPress.org “Guide to Sanitization” (what sanitize_text_field() does; sanitize_key() for slug-form data) (make.wordpress.org)
[3] WordPress core changeset requiring valid action for admin-ajax.php (core.trac.wordpress.org)
Citations:
- 1: https://developer.wordpress.org/reference/functions/sanitize_key/?utm_source=openai
- 2: https://make.wordpress.org/themes/2015/06/02/a-guide-to-writing-secure-themes-part-3-sanitization/?utm_source=openai
- 3: https://core.trac.wordpress.org/changeset/52813?utm_source=openai
Use explicit $_POST and sanitize_key() for action detection.
Line 206 reads action from $_REQUEST, which is ambiguous (merges GET, POST, cookie with unpredictable precedence). For AJAX handling, use explicit $_POST['action']. Additionally, action identifiers should use sanitize_key() instead of sanitize_text_field(), as actions are slug-like keys not user text.
Suggested change
$get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
- $request_act = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : '';
+ $post_action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : '';
$is_elementor = ( $get_action === 'elementor' )
- || ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && $request_act === 'elementor_ajax' );
+ || ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && $post_action === 'elementor_ajax' );📝 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.
| $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; | |
| $request_act = isset( $_REQUEST['action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['action'] ) ) : ''; | |
| $is_elementor = ( $get_action === 'elementor' ) | |
| || ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && $request_act === 'elementor_ajax' ); | |
| $get_action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : ''; | |
| $post_action = isset( $_POST['action'] ) ? sanitize_key( wp_unslash( $_POST['action'] ) ) : ''; | |
| $is_elementor = ( $get_action === 'elementor' ) | |
| || ( ( defined( 'DOING_AJAX' ) && DOING_AJAX ) && $post_action === 'elementor_ajax' ); |
🧰 Tools
🪛 GitHub Check: Run PHPCS inspection
[warning] 206-206:
Processing form data without nonce verification.
[warning] 206-206:
Processing form data without nonce verification.
[warning] 205-205:
Processing form data without nonce verification.
[warning] 205-205:
Processing form data without nonce verification.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@wpuf.php` around lines 205 - 208, The code reads action ambiguously from
$_REQUEST and uses sanitize_text_field; change the detection to explicitly read
$_GET['action'] into $get_action and $_POST['action'] into a new $post_action
(instead of $request_act), and use sanitize_key() when normalizing both action
values; update the $is_elementor condition to check $get_action === 'elementor'
|| ( defined('DOING_AJAX') && DOING_AJAX && $post_action === 'elementor_ajax' )
to avoid $_REQUEST and use slug-safe sanitization.
closes #1423 , closes #1375, closes #1386, closes #1387, closes #1398, closes #1399
Related PR in pro #1425
Summary
This PR adds an Elementor widget that allows you to display WP User Frontend forms directly in the Elementor page builder. You can now drag and drop any WPUF form onto your Elementor pages and customize its appearance using Elementor's native style controls.
What's New
New "User Frontend Form" widget in Elementor with its own widget category
Select any published WPUF form from a dropdown in the widget settings
Customize form styling directly in Elementor including:
Labels, inputs, and placeholders
Radio and checkbox fields
Upload and submit buttons
Container spacing and typography
Forms render correctly in both the Elementor editor preview and on the frontend
Technical Notes
Forms continue to use the existing [wpuf_form] shortcode rendering logic
New filter hooks for extending the widget:
wpuf_elementor_styles_to_enqueue- Customize loaded stylesheetswpuf_elementor_widget_style_depends- Add widget style dependencieswpuf_elementor_widget_script_depends- Add widget script dependencieswpuf_elementor_form_options- Modify the form dropdown optionswpuf_elementor_widget_register_style_controls- Add custom style sections (e.g., for multistep forms in Pro)Summary by CodeRabbit
Release Notes
New Features
Bug Fixes