Elementor widgets#324
Conversation
Register and include several new Elementor widgets and add AJAX endpoints/handlers. - Added new Elementor widgets: DocsSidebar, NeedHelp, SearchModal, TableOfContents, WasThisHelpful (full widget implementations added under includes/Elementor/Widgets). - Updated includes/Elementor.php to require and register the new widgets with Elementor. - Extended includes/Ajax.php to register AJAX actions and implemented handlers for: - wedocs_helpful_vote: records yes/no votes using post meta (_wedocs_helpful_yes/_wedocs_helpful_no) with nonce verification. - wedocs_helpful_feedback: saves textual negative feedback into post meta (_wedocs_helpful_feedback) with nonce verification. - wedocs_need_help_submit: processes the "Need More Help" contact form (nonce check per widget), sends email to recipient (falls back to admin email), and optionally saves submission to Elementor Pro submissions if available. - Added private helper save_to_elementor_submissions() to persist form entries to Elementor Pro Submissions DB (checks for Elementor Pro class first). Security: nonces and input sanitization are applied; email/recipient fallbacks handled. Widgets include styles, scripts and content templates for Elementor editor preview.
Register and include three new Elementor widgets (DocNavigation, DocsBreadcrumb, DocsHamburgerMenu) by updating includes/Elementor.php. Add full widget implementations for next/previous doc navigation, docs breadcrumb, and an off-canvas/hamburger docs menu including controls, rendering, editor preview templates and inline styles. Also adjust responsive styles in src/assets/less/responsive.less to support the new widgets.
Switch feedback storage to unified 'positive'/'negative' meta keys (Ajax, admin metabox, list table) and update code to read/write those keys. Add editor-aware context and preview rendering for Elementor widgets (DocNavigation, DocsBreadcrumb, DocsSidebar), plus new responsive controls (mobile stacking/toggle/hamburger) and related CSS for better editor UX and mobile behavior. Include a new three_column Elementor template and multiple formatting/whitespace cleanups across API and admin code. These changes improve consistency of feedback data and provide accurate previews in Elementor editor.
Register template import flow and editor scripts for weDocs. Adds init_templates hook, admin-only enqueue for Elementor editor, and functions to import a three_column template from JSON, fix/normalize existing template meta formats, set template conditions, reset imports for development, and get template version. Marks imported templates with _wedocs_template and uses an option (wedocs_elementor_templates_imported) to avoid re-importing. Also includes minor code style tweaks and updates three_column.json to a validated, pretty-printed template format.
|
Warning Review limit reached
Next review available in: 33 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughAdds Elementor widgets, AJAX interactions, template importing with a bundled three-column template, document-tree navigation, configurable single-document renderers, responsive search styling, and minor API/admin adjustments. ChangesElementor Documentation Experience
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✨ 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: 12
🧹 Nitpick comments (2)
includes/Elementor.php (1)
117-120: ⚡ Quick winAvoid normalizing template meta on every request.
fix_existing_template_conditions()runs before the import flag check, so everyinitperforms an unboundedelementor_libraryquery while Elementor is active. Gate this behind a versioned “normalized” option or run it only from an admin/upgrade path.Also applies to: 158-164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Elementor.php` around lines 117 - 120, The `fix_existing_template_conditions()` method is being called on every request during init, which causes unnecessary unbounded queries to the elementor_library table. Add a versioned option check before calling this method in `import_default_templates()` to track whether the template conditions have already been normalized. Only execute `fix_existing_template_conditions()` if the stored version is outdated or the option doesn't exist yet, and update the option after successful execution. This prevents the expensive operation from running on every init and improves performance.includes/Elementor/Widgets/NeedHelp.php (1)
821-821: 💤 Low valueConsider handling the case when
get_the_ID()returns false.If this widget is used outside a post context (e.g., on a custom Elementor template not tied to a post),
get_the_ID()returnsfalse, which becomes the string'false'in JavaScript. This is sent to the server aspost_id: 'false'.🛡️ Suggested defensive fix
- post_id: <?php echo get_the_ID(); ?>, + post_id: <?php echo (int) get_the_ID(); ?>,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Elementor/Widgets/NeedHelp.php` at line 821, The post_id field in the JavaScript object assignment is vulnerable to receiving false when get_the_ID() returns false outside a post context, which converts to the string 'false' in JavaScript. Add a defensive check to handle the case when get_the_ID() returns false, either by conditionally including the post_id field only when a valid ID exists, or by using a ternary operator to provide a fallback value (such as null or 0) when no post context is available. This ensures that invalid post_id values are not sent to the server.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@includes/Ajax.php`:
- Around line 593-611: The handle_helpful_vote() method lacks duplicate vote
prevention mechanisms that exist in the handle_helpful_feedback_vote() method.
Add validation to check if the post is of type 'docs' using get_post_type()
similar to line 417. Then implement duplicate vote prevention by checking for
existing votes using cookie-based tracking (via isset($_COOKIE)), user meta
checks (if user is logged in), and IP-based meta checks to prevent the same user
or IP from voting multiple times on the same post. Store the vote tracking
information using update_post_meta() or setcookie() as appropriate after a
successful vote, and return an error response if a duplicate vote is detected
before incrementing the vote counts.
- Around line 616-639: The handle_helpful_feedback method lacks post type
validation, allowing feedback to be submitted for any post ID rather than only
docs post type. After retrieving the post_id and before processing the feedback,
add validation to check that the post exists and is of type docs using get_post
and verify its post_type property equals docs, returning an appropriate error if
validation fails, similar to the pattern used in handle_helpful_vote.
In `@includes/Elementor.php`:
- Around line 139-151: The template import sequence has a race condition where
concurrent requests can both pass the initial
`get_option('wedocs_elementor_templates_imported')` check and proceed to call
`import_template_from_json()`. Replace the initial `get_option()` check with an
atomic `add_option()` call that attempts to claim a lock for the import
process—if `add_option()` returns false (indicating the option already exists),
return early. This ensures only one request can proceed past the lock and
execute the `import_template_from_json()` and `set_template_conditions()` calls,
eliminating the race window.
- Around line 144-151: The `set_template_conditions()` call for 'single_docs' is
being applied immediately after importing the template without checking for
existing conflicting Theme Builder templates, which can cause condition
conflicts on Elementor Pro sites. Before calling
`set_template_conditions($template_id, 'single_docs')`, implement a check to
verify that no existing Elementor templates already target the same
'single_docs' conditions. If conflicting templates are detected, either skip the
auto-assignment of conditions or defer it to an explicit admin action during
onboarding rather than automatic import. Apply this same logic to the additional
location mentioned (lines 265-275) where similar template condition assignments
occur.
- Around line 203-207: The `_elementor_data` JSON being stored via
`update_post_meta()` and `meta_input` in `wp_insert_post()` needs to be wrapped
with `wp_slash()` to prevent WordPress from stripping escaped quotes and
backslashes during storage. Modify the `update_post_meta()` call to wrap
`wp_json_encode($elementor_data)` with `wp_slash()`, and apply the same fix to
the `meta_input` array entry for `_elementor_data` around line 270 in the same
file.
In `@includes/Elementor/Templates/three_column.json`:
- Around line 122-123: Remove or replace the hardcoded developer email address
in the recipient_email field within the three_column.json template. The field
currently contains "dev-email@wpengine.local" which will cause contact form
submissions from imported sites to be sent to the wrong destination. Either set
the recipient_email value to an empty string, a placeholder value, or remove the
field entirely so users must configure their own email address during site
import.
In `@includes/Elementor/Widgets/DocNavigation.php`:
- Around line 266-286: The selectors in the title_color and title_hover_color
controls are targeting a non-existent anchor element structure. The selectors
currently use {{WRAPPER}} .wedocs-el-nav__title a and {{WRAPPER}}
.wedocs-el-nav__title a:hover, but the actual rendered output places plain text
directly inside .wedocs-el-nav__title without an anchor wrapper. Update both
selector strings to target .wedocs-el-nav__title directly (removing the a tag
reference and changing a:hover to just :hover) to match the actual rendered HTML
structure so the color controls will apply properly.
- Around line 400-413: The prev and next post lookup queries in the
DocNavigation widget use only menu_order for comparison, which fails when
sibling documents share the same menu_order value. Fix the $next_query and
$prev_query strings by adding a deterministic ID-based tie-breaker: append AND
ID > {$current_post->ID} to the $next_query condition and AND ID <
{$current_post->ID} to the $prev_query condition. Additionally, convert both
queries from direct string interpolation to use $wpdb->prepare() for safe
parameterized query execution instead of the current concatenation approach.
In `@includes/Elementor/Widgets/DocsBreadcrumb.php`:
- Around line 377-384: The while loop in the breadcrumb traversal does not check
if get_post($parent_id) returns null before accessing $page properties on lines
380 and 383. Add a null check immediately after the get_post() call to verify
that $page exists, and if it does not (indicating an orphaned post_parent),
break out of the loop to prevent null reference errors when accessing $page->ID
and dereferencing it for get_the_title() and get_permalink().
- Around line 390-393: In the DocsBreadcrumb widget where the current breadcrumb
item is being added to the items array, replace the `get_the_title()` call
(which relies on global post state) with a call to get the title from the
resolved docs context. This ensures the correct current label is displayed in
editor and fallback contexts where the global post state may be unreliable.
Identify what the resolved docs context variable or method is in the class and
use that instead of the global post-dependent function.
In `@includes/Elementor/Widgets/DocsHamburgerMenu.php`:
- Around line 404-416: The code in the DocsHamburgerMenu widget directly
accesses properties on the global $post variable without first verifying that
$post is a valid WP_Post object. Add a guard check at the beginning of this
method to verify that $post exists and is an object before proceeding with the
logic that accesses $post->post_parent and $post->ID. If $post is not valid, the
method should handle this gracefully by returning early or using a safe default
value instead of attempting to access the parent document logic.
- Around line 458-460: In the panel header link section where the parent post is
rendered, apply proper output escaping to both the URL and text content. Wrap
the get_permalink($parent) function call with esc_url() to safely escape the
href attribute value, and wrap the get_post_field('post_title', $parent,
'display') call with esc_html() to safely escape the displayed title text within
the anchor tag.
---
Nitpick comments:
In `@includes/Elementor.php`:
- Around line 117-120: The `fix_existing_template_conditions()` method is being
called on every request during init, which causes unnecessary unbounded queries
to the elementor_library table. Add a versioned option check before calling this
method in `import_default_templates()` to track whether the template conditions
have already been normalized. Only execute `fix_existing_template_conditions()`
if the stored version is outdated or the option doesn't exist yet, and update
the option after successful execution. This prevents the expensive operation
from running on every init and improves performance.
In `@includes/Elementor/Widgets/NeedHelp.php`:
- Line 821: The post_id field in the JavaScript object assignment is vulnerable
to receiving false when get_the_ID() returns false outside a post context, which
converts to the string 'false' in JavaScript. Add a defensive check to handle
the case when get_the_ID() returns false, either by conditionally including the
post_id field only when a valid ID exists, or by using a ternary operator to
provide a fallback value (such as null or 0) when no post context is available.
This ensures that invalid post_id values are not sent to the server.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0806c1e3-d6e6-4ea5-9024-1a17ca1b9e69
📒 Files selected for processing (14)
includes/API/API.phpincludes/Admin/Docs_List_Table.phpincludes/Ajax.phpincludes/Elementor.phpincludes/Elementor/Templates/three_column.jsonincludes/Elementor/Widgets/DocNavigation.phpincludes/Elementor/Widgets/DocsBreadcrumb.phpincludes/Elementor/Widgets/DocsHamburgerMenu.phpincludes/Elementor/Widgets/DocsSidebar.phpincludes/Elementor/Widgets/NeedHelp.phpincludes/Elementor/Widgets/SearchModal.phpincludes/Elementor/Widgets/TableOfContents.phpincludes/Elementor/Widgets/WasThisHelpful.phpsrc/assets/less/responsive.less
| $template_id = $this->import_template_from_json('three_column', 'weDocs - Single Doc Page'); | ||
|
|
||
| if ($template_id) { | ||
| // Set template conditions for single docs | ||
| $this->set_template_conditions($template_id, 'single_docs'); | ||
|
|
||
| // Mark as imported | ||
| update_option('wedocs_elementor_templates_imported', true); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "Elementor.php" -type fRepository: weDevsOfficial/wedocs-plugin
Length of output: 97
🏁 Script executed:
wc -l includes/Elementor.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 99
🏁 Script executed:
sed -n '140,160p' includes/Elementor.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 762
🏁 Script executed:
sed -n '260,280p' includes/Elementor.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 1037
🏁 Script executed:
sed -n '1,335p' includes/Elementor.php | grep -n "set_template_conditions\|import_template_from_json" -A 20 | head -80Repository: weDevsOfficial/wedocs-plugin
Length of output: 2566
🏁 Script executed:
grep -n "function set_template_conditions\|function import_template_from_json" includes/Elementor.php -A 30Repository: weDevsOfficial/wedocs-plugin
Length of output: 2092
🌐 Web query:
Elementor Pro theme builder template precedence multiple templates single doc page
💡 Result:
In Elementor Pro, when multiple Theme Builder templates are eligible to be applied to a single page (i.e., they meet the display conditions), Elementor does not use a user-configurable priority order to resolve them [1][2]. Instead, it uses a deterministic hierarchy system based on the specificity of the conditions applied [1][3]. Key mechanisms governing template precedence include: Specificity Over Generality: Elementor follows a principle where more specific conditions take precedence over broader ones [1][3]. For example, a template with a condition set to a specific page or category will typically override a template with a broader "Entire Site" condition [4]. Condition Tree and Hierarchy: Elementor’s condition system is modeled after the WordPress Template Hierarchy [1][3]. When resolving which template to render, Elementor evaluates conditions based on their registered priority and group type [3]. While developers can register custom conditions and assign them a numerical priority (ranging from 0 to 100), the default behavior for built-in templates is to resolve based on the specificity of the match [3]. Exclusions: If a template is explicitly excluded from a specific page, that exclusion will override any inclusion condition, even if the inclusion condition is more specific [1]. Conflict Resolution: If two templates have identical specificity (e.g., both are set to "Entire Site"), Elementor's behavior is typically consistent but not user-configurable [2]. In such scenarios, if you encounter conflicts, the recommended best practice is to: 1. Audit your display conditions: Ensure that templates are not targeting overlapping sets of pages unintentionally [4]. 2. Use Exclusions: Explicitly exclude a template from pages where it should not appear to prevent it from conflicting with a more preferred template [1]. 3. Consolidate Templates: Where possible, merge conflicting templates into a single template that uses conditional logic or dynamic content to handle different page layouts, rather than maintaining multiple competing templates [5]. Note: Some third-party themes or Elementor Addon plugins may implement their own template priority managers, which can override Elementor’s default behavior [2]. If you are using such a plugin, you must check that specific plugin's settings to control template precedence [2].
Citations:
- 1: https://developers.elementor.com/docs/theme-conditions/
- 2: https://docs.cmsmasters.net/how-to-manage-template-priority-between-cmsmasters-elementor-addon-and-elementor-pro/
- 3: https://developers.elementor.com/docs/theme-conditions/condition-group-type/
- 4: https://elementor.com/help/the-elementor-theme-builder/
- 5: https://elementor.com/help/single-page-site-part/
🌐 Web query:
Elementor Pro elementor_library post status draft vs publish best practices bundled templates
💡 Result:
In Elementor, templates are stored in the WordPress database as custom posts with the post type elementor_library [1]. Understanding the difference between draft and publish statuses for these items is critical for both site management and functional reliability. Best Practices for Post Status 1. Published Status (Recommended for Active Templates): If a template is intended to be used as a global part of your site—such as a header, footer, single post layout, or maintenance mode page—it should be set to Publish [2]. Elementor features that rely on dynamic assignment (like Theme Builder locations) often require the template to be published to function correctly or to be recognized in site-wide settings [2]. 2. Draft Status (Recommended for Work-in-Progress): Keeping templates in Draft status is best practice while you are actively designing or testing new layouts. This prevents them from being accidentally selected or interfering with live site settings. However, be aware that some users have reported that Elementor’s interface may still flag or warn about template conflicts even if the conflicting template is in Draft status [3]. If you encounter persistent issues with template assignments, ensure the unnecessary draft is completely removed (trashed) rather than just saved as a draft [3]. Key Functional Differences - Active Integration: Elements like Maintenance Mode, 404 pages, or Header/Footer builders generally require the template to have a Published status to successfully bind to the site's front-end display [2]. - Reusability: When saving a page as a template for manual insertion (using the Library icon in the editor), the status of the template is less critical for the insertion process itself, but keeping your library organized by publishing only "ready" versions is a recommended workflow habit to avoid confusion [4][5]. - Database Handling: Because Elementor templates are standard WordPress posts, they follow standard WordPress post status behavior [1]. You can manage them via the standard WordPress dashboard or the Elementor Templates menu [1]. General Workflow Advice - Organize and Cleanup: Regularly audit your Elementor Library. If a draft is no longer needed, move it to the trash to avoid potential configuration conflicts or warnings in the Theme Builder [3]. - Testing: Before publishing a new site-wide template, use the Preview functionality within the Elementor editor to ensure it displays correctly [5]. Once verified, update the status to Publish to make it active for your site's visitors [5]. If you are using programmatic methods (such as the WP-CLI or REST API) to manage templates, always verify the status field, as automated processes often expect a Publish status to successfully apply theme templates or site-wide overrides [2][6].
Citations:
- 1: https://themewaves.com/how-to-find-your-template-drafts-in-elementor/
- 2: https://github.com/mwender/elementor-mcp-skill/blob/main/references/maintenance-mode.md
- 3: Bug report: Theme builder does not ignore draft templates elementor/elementor#8229
- 4: https://elementor.com/help/create-templates-for-faster-website-building/
- 5: https://www.likacloud.com/en/docs/wordpress/wp-themes/elementor-save-publish-tips/
- 6: Internal: REST API for Elementor post meta, user meta and settings [ED-17682] elementor/elementor#29693
🏁 Script executed:
grep -r "elementor_conditions\|theme.*builder\|precedence" includes/ --include="*.php" | head -20Repository: weDevsOfficial/wedocs-plugin
Length of output: 405
Auto-apply template conditions only after checking for existing Theme Builder templates.
On first init, this template is published and immediately assigned to include/single/docs conditions, which can conflict with existing Theme Builder templates targeting the same doc pages on Elementor Pro sites. Elementor's template precedence is based on condition specificity—when two templates have identical specificity (both targeting all single docs), conflict resolution is not user-configurable.
Instead of relying on draft status (which breaks Theme Builder integration), verify no conflicting templates exist before auto-assigning conditions, or defer condition assignment to explicit admin action during onboarding.
Safer approach: Check for existing templates before applying conditions
if ($template_id) {
- // Set template conditions for single docs
- $this->set_template_conditions($template_id, 'single_docs');
+ // Check for existing templates targeting single docs before auto-applying
+ $existing = get_posts([
+ 'post_type' => 'elementor_library',
+ 'meta_key' => '_elementor_conditions',
+ 'meta_value' => 'include/single/docs',
+ ]);
+
+ if (empty($existing)) {
+ // Only auto-apply if no conflicting templates exist
+ $this->set_template_conditions($template_id, 'single_docs');
+ }
// Mark as imported
update_option('wedocs_elementor_templates_imported', true);
}Also applies to: 265-275
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Elementor.php` around lines 144 - 151, The
`set_template_conditions()` call for 'single_docs' is being applied immediately
after importing the template without checking for existing conflicting Theme
Builder templates, which can cause condition conflicts on Elementor Pro sites.
Before calling `set_template_conditions($template_id, 'single_docs')`, implement
a check to verify that no existing Elementor templates already target the same
'single_docs' conditions. If conflicting templates are detected, either skip the
auto-assignment of conditions or defer it to an explicit admin action during
onboarding rather than automatic import. Apply this same logic to the additional
location mentioned (lines 265-275) where similar template condition assignments
occur.
| // Check if elementor data is stored as array instead of JSON string | ||
| if (is_array($elementor_data) && !empty($elementor_data)) { | ||
| // Convert from array to JSON string | ||
| update_post_meta($template->ID, '_elementor_data', wp_json_encode($elementor_data)); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Expectation: _elementor_data writes that persist JSON use wp_slash(wp_json_encode(...)).
rg -n -C2 "_elementor_data|wp_json_encode" --type=phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 7667
🏁 Script executed:
# Check if wp_slash is used elsewhere in the codebase, particularly with post-meta
rg -n "wp_slash" --type=php -A2 -B2Repository: weDevsOfficial/wedocs-plugin
Length of output: 54
🏁 Script executed:
# Search for other post-meta updates with JSON data in the codebase
rg -n "update_post_meta.*json|update_post_meta.*encode" --type=php -iRepository: weDevsOfficial/wedocs-plugin
Length of output: 200
🏁 Script executed:
# Check how _elementor_data is being retrieved/used after being stored
rg -n "get_post_meta.*_elementor_data" --type=php -A3Repository: weDevsOfficial/wedocs-plugin
Length of output: 792
🌐 Web query:
WordPress wp_slash post-meta unslashing behavior sanitization
💡 Result:
In WordPress, post metadata functions such as update_post_meta and add_post_meta internally apply stripslashes to the input data before storing it in the database [1][2]. This historical behavior means that if your data contains escaped characters (such as JSON strings with escaped quotes), those backslashes will be removed, often corrupting the intended format [1][2][3]. To prevent this data corruption, you must use wp_slash on your data before passing it to these metadata functions [4][1]. The wp_slash function adds an extra layer of escaping (backslashes) that compensates for the subsequent stripping performed by the metadata API [1][2]. Key points regarding this behavior: 1. Requirement: If you are providing raw or already-escaped data (like a JSON-encoded string) to functions like update_post_meta, you must wrap that data in wp_slash [1][2][3]. 2. Purpose: wp_slash is specifically intended for preparing data for WordPress core APIs that expect "slashed" input [5]. Conversely, wp_unslash should be used when you need to remove slashes, for example when retrieving data from sources like the $_POST superglobal, which WordPress automatically escapes upon request arrival [6]. 3. Caveats: While wp_slash and wp_unslash are intended to be inverses, they are not perfectly symmetrical in all cases, particularly regarding the handling of objects and nested structures [7]. Specifically, wp_unslash utilizes map_deep, which recursively traverses objects, whereas wp_slash does not [7]. 4. Best Practice: Always check the documentation for the specific API you are using, as some newer APIs or alternative functions may not follow this "slashed" input expectation [8]. However, for the core metadata functions, wp_slash is the standard workaround to ensure data integrity [1][2].
Citations:
- 1: https://developer.wordpress.org/reference/functions/update_post_meta/
- 2: https://developer.wordpress.org/plugins/metadata/managing-post-metadata/
- 3: https://wordpress.stackexchange.com/questions/53336/wordpress-is-stripping-escape-backslashes-from-json-strings-in-post-meta
- 4: https://core.trac.wordpress.org/ticket/56832
- 5: https://developer.wordpress.org/reference/functions/wp_slash/?output_format=md
- 6: https://wp-kama.com/function/wp_unslash
- 7: https://core.trac.wordpress.org/ticket/56119
- 8: https://core.trac.wordpress.org/ticket/21767
🏁 Script executed:
# Check the context around line 270 to see the full wp_insert_post call
sed -n '260,280p' includes/Elementor.phpRepository: weDevsOfficial/wedocs-plugin
Length of output: 1037
🌐 Web query:
WordPress wp_insert_post meta_input stripslashes behavior
💡 Result:
The wp_insert_post function (and by extension its meta_input array) is designed to handle slashed data for historical compatibility reasons [1][2]. Because WordPress historically relied on magic quotes, the core API functions like wp_insert_post and update_post_meta expect input to be slashed [1][3][2]. When you pass data through the meta_input array, it is passed to update_post_meta [4][5], which in turn processes the data using internal functions that expect it to be escaped [1]. Consequently, if you provide unslashed data (containing backslashes) to these functions, WordPress will effectively "strip" those slashes during its internal processing [3]. To ensure your data is stored exactly as intended, you should use wp_slash on your data array before passing it to wp_insert_post [3][2]. Example usage: wp_insert_post( wp_slash( array( 'post_title' => 'My Title', 'meta_input' => array( 'my_meta_key' => 'Value with \ backslashes',),))); In this example, wp_slash ensures that the backslashes are properly escaped before the internal logic removes them [3][2]. If you do not apply wp_slash, the backslashes in your meta value may be removed or corrupted [3].
Citations:
- 1: https://core.trac.wordpress.org/ticket/21767
- 2: https://make.xwp.co/2015/11/11/use-meta_input-and-tax_input-with-wp_insert_post/
- 3: https://core.trac.wordpress.org/ticket/54601
- 4: https://developer.wordpress.org/reference/functions/wp_insert_post/
- 5: https://wordpress.stackexchange.com/questions/216178/what-is-meta-input-parameter-in-wp-insert-post-used-for
Wrap _elementor_data JSON with wp_slash() before saving to post meta.
WordPress's post-meta functions (update_post_meta, add_post_meta, and wp_insert_post with meta_input) internally apply stripslashes() before storing data in the database. Without wp_slash(), JSON-encoded data containing escaped quotes or backslashes will be corrupted during storage.
This affects two locations:
- Line 206:
update_post_meta()call - Line 270:
meta_inputarray inwp_insert_post()
Suggested fixes
- update_post_meta($template->ID, '_elementor_data', wp_json_encode($elementor_data));
+ update_post_meta($template->ID, '_elementor_data', wp_slash(wp_json_encode($elementor_data)));- '_elementor_data' => wp_json_encode($template_data['content']),
+ '_elementor_data' => wp_slash(wp_json_encode($template_data['content'])),📝 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.
| // Check if elementor data is stored as array instead of JSON string | |
| if (is_array($elementor_data) && !empty($elementor_data)) { | |
| // Convert from array to JSON string | |
| update_post_meta($template->ID, '_elementor_data', wp_json_encode($elementor_data)); | |
| } | |
| // Check if elementor data is stored as array instead of JSON string | |
| if (is_array($elementor_data) && !empty($elementor_data)) { | |
| // Convert from array to JSON string | |
| update_post_meta($template->ID, '_elementor_data', wp_slash(wp_json_encode($elementor_data))); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Elementor.php` around lines 203 - 207, The `_elementor_data` JSON
being stored via `update_post_meta()` and `meta_input` in `wp_insert_post()`
needs to be wrapped with `wp_slash()` to prevent WordPress from stripping
escaped quotes and backslashes during storage. Modify the `update_post_meta()`
call to wrap `wp_json_encode($elementor_data)` with `wp_slash()`, and apply the
same fix to the `meta_input` array entry for `_elementor_data` around line 270
in the same file.
| if ($show_next) { | ||
| $next_query = "SELECT ID FROM {$wpdb->posts} | ||
| WHERE post_parent = {$current_post->post_parent} AND post_type = 'docs' AND post_status = 'publish' AND menu_order > {$current_post->menu_order} | ||
| ORDER BY menu_order ASC | ||
| LIMIT 0, 1"; | ||
| $next_post_id = (int) $wpdb->get_var($next_query); | ||
| } | ||
|
|
||
| if ($show_prev) { | ||
| $prev_query = "SELECT ID FROM {$wpdb->posts} | ||
| WHERE post_parent = {$current_post->post_parent} AND post_type = 'docs' AND post_status = 'publish' AND menu_order < {$current_post->menu_order} | ||
| ORDER BY menu_order DESC | ||
| LIMIT 0, 1"; | ||
| $prev_post_id = (int) $wpdb->get_var($prev_query); |
There was a problem hiding this comment.
Prev/next lookup fails when sibling docs share menu_order.
Line 402 and Line 410 compare only menu_order (>/<). If siblings have equal menu_order (common default), no adjacent doc is found. Add a deterministic tie-break using ID and prepare the query parameters.
Suggested fix
- $next_query = "SELECT ID FROM {$wpdb->posts}
- WHERE post_parent = {$current_post->post_parent} AND post_type = 'docs' AND post_status = 'publish' AND menu_order > {$current_post->menu_order}
- ORDER BY menu_order ASC
- LIMIT 0, 1";
- $next_post_id = (int) $wpdb->get_var($next_query);
+ $next_query = $wpdb->prepare(
+ "SELECT ID FROM {$wpdb->posts}
+ WHERE post_parent = %d
+ AND post_type = 'docs'
+ AND post_status = 'publish'
+ AND (menu_order > %d OR (menu_order = %d AND ID > %d))
+ ORDER BY menu_order ASC, ID ASC
+ LIMIT 1",
+ (int) $current_post->post_parent,
+ (int) $current_post->menu_order,
+ (int) $current_post->menu_order,
+ (int) $current_post->ID
+ );
+ $next_post_id = (int) $wpdb->get_var( $next_query );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/Elementor/Widgets/DocNavigation.php` around lines 400 - 413, The
prev and next post lookup queries in the DocNavigation widget use only
menu_order for comparison, which fails when sibling documents share the same
menu_order value. Fix the $next_query and $prev_query strings by adding a
deterministic ID-based tie-breaker: append AND ID > {$current_post->ID} to the
$next_query condition and AND ID < {$current_post->ID} to the $prev_query
condition. Additionally, convert both queries from direct string interpolation
to use $wpdb->prepare() for safe parameterized query execution instead of the
current concatenation approach.
Replace per-parent sibling SQL with tree-aware navigation and add a selectable single-doc renderer. Introduces wedocs_get_doc_root_id() and wedocs_get_doc_tree_order() to build a depth-first reading order (ordered by menu_order, then title) so next/prev cross section boundaries. wedocs_get_doc_navigation_posts() now uses that order; DocNavigation widget simplified to consume the helper. Add renderer resolution APIs (wedocs_get_single_doc_renderer, wedocs_has_elementor_single_doc_template) and keep a back-compat shim for use_wedocs_legacy_template(). SingleDocTemplate is gated to run only when the block renderer is selected. Admin UI updated: replace legacy toggle with a dropdown (Classic / Block / Elementor) that keeps the legacy flag in sync.
excape url, title Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
includes/Elementor/Widgets/DocsHamburgerMenu.php (1)
686-696: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve body locking across multiple widget instances.
Closing one hamburger menu unconditionally clears
bodyoverflow, even if another menu instance is still active. Check for other active panels before restoring the previous overflow state.Suggested fix
+ var previousBodyOverflow = ''; + function openPanel() { + if (!$panel.hasClass('active')) { + previousBodyOverflow = $('body').css('overflow'); + } $panel.addClass('active'); $overlay.addClass('active'); $('body').css('overflow', 'hidden'); } function closePanel() { $panel.removeClass('active'); $overlay.removeClass('active'); - $('body').css('overflow', ''); + if (!$('.wedocs-hamburger__panel.active').length) { + $('body').css('overflow', previousBodyOverflow); + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Elementor/Widgets/DocsHamburgerMenu.php` around lines 686 - 696, Update closePanel() to restore the body overflow only when no other hamburger menu panels remain active; otherwise preserve the body lock established by openPanel(). Check for active panel instances globally before clearing $('body').css('overflow', '').
🧹 Nitpick comments (1)
includes/Elementor/Widgets/DocsHamburgerMenu.php (1)
439-477: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd accessible state and names to the off-canvas controls.
Add
aria-controlsand dynamically updatedaria-expandedto the trigger,aria-hidden/dialog semantics to the panel, and an accessible label for.search-submit. This will expose open/closed state and the search action to assistive technology.Also applies to: 702-706
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Elementor/Widgets/DocsHamburgerMenu.php` around lines 439 - 477, Update the hamburger trigger button to include aria-controls referencing its panel and dynamically maintain aria-expanded as the panel opens or closes. Mark the matching .wedocs-hamburger__panel as a dialog with aria-hidden reflecting its visibility, and add an accessible label to the .search-submit button. Apply the same accessibility attributes and state updates to the corresponding control instance around the additional referenced section.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@includes/Elementor/Widgets/DocsHamburgerMenu.php`:
- Around line 686-696: Update closePanel() to restore the body overflow only
when no other hamburger menu panels remain active; otherwise preserve the body
lock established by openPanel(). Check for active panel instances globally
before clearing $('body').css('overflow', '').
---
Nitpick comments:
In `@includes/Elementor/Widgets/DocsHamburgerMenu.php`:
- Around line 439-477: Update the hamburger trigger button to include
aria-controls referencing its panel and dynamically maintain aria-expanded as
the panel opens or closes. Mark the matching .wedocs-hamburger__panel as a
dialog with aria-hidden reflecting its visibility, and add an accessible label
to the .search-submit button. Apply the same accessibility attributes and state
updates to the corresponding control instance around the additional referenced
section.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b767a646-a8c0-45f2-a72f-7b5969cb62b9
📒 Files selected for processing (1)
includes/Elementor/Widgets/DocsHamburgerMenu.php
…/wedocs-plugin into elementor-widgets
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
includes/Templates/SingleDocTemplate.php (1)
45-50: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winCheck the request type before resolving the renderer.
This hook runs on every frontend request, while automatic renderer resolution may query Elementor templates. Preserve the previous cheap exit for non-doc and embedded requests before invoking the resolver.
Proposed fix
public function render_block_template() { + if ( is_embed() || ! is_singular( 'docs' ) ) { + return; + } + if ( 'block' !== wedocs_get_single_doc_renderer() ) { return; } - if ( ! is_embed() && is_singular( 'docs' ) ) { + {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/Templates/SingleDocTemplate.php` around lines 45 - 50, Move the non-doc and embedded-request guard in the hook before the wedocs_get_single_doc_renderer() call. Only resolve and compare the renderer after confirming ! is_embed() && is_singular( 'docs' ), while preserving the existing block-renderer check and behavior.includes/functions.php (1)
302-316: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftAvoid rebuilding the entire documentation forest on every navigation render.
This hot path fetches and groups every published doc, including unrelated roots. Cache tree orders with invalidation on doc changes, or scope retrieval to the requested root, so single-doc rendering does not scale linearly with the whole knowledge base.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@includes/functions.php` around lines 302 - 316, Update the navigation tree-building flow around the $wpdb->get_results query to avoid fetching and regrouping every published docs page on each render. Either cache the computed tree/order data and invalidate it whenever docs change, or constrain retrieval to the requested documentation root while preserving the existing menu_order ASC, post_title ASC ordering and next/previous behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@includes/functions.php`:
- Around line 1257-1273: Update the renderer resolution around the modern
setting and legacy fallback in includes/functions.php: consult
use_legacy_template only when single_doc_template is absent, and let an
unavailable explicit renderer continue directly to automatic detection. In
src/components/Settings/GeneralSettings.js, represent an unset renderer as the
automatic/effective selection instead of defaulting to block, and do not use the
binary legacy field as the Elementor source of truth.
- Around line 264-275: Update wedocs_get_doc_root_id to track each visited post
ID and return 0 immediately when an ID repeats, preventing cyclic hierarchies
from returning a node within the cycle. Remove the fixed 50-iteration guard so
valid trees deeper than 50 levels continue resolving normally, while preserving
the existing root termination behavior.
---
Nitpick comments:
In `@includes/functions.php`:
- Around line 302-316: Update the navigation tree-building flow around the
$wpdb->get_results query to avoid fetching and regrouping every published docs
page on each render. Either cache the computed tree/order data and invalidate it
whenever docs change, or constrain retrieval to the requested documentation root
while preserving the existing menu_order ASC, post_title ASC ordering and
next/previous behavior.
In `@includes/Templates/SingleDocTemplate.php`:
- Around line 45-50: Move the non-doc and embedded-request guard in the hook
before the wedocs_get_single_doc_renderer() call. Only resolve and compare the
renderer after confirming ! is_embed() && is_singular( 'docs' ), while
preserving the existing block-renderer check and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 88efe690-e32d-4437-84dd-7119d83252e1
📒 Files selected for processing (4)
includes/Elementor/Widgets/DocNavigation.phpincludes/Templates/SingleDocTemplate.phpincludes/functions.phpsrc/components/Settings/GeneralSettings.js
| // 1. Explicit modern setting wins. | ||
| if ( ! empty( $general_settings['single_doc_template'] ) | ||
| && in_array( $general_settings['single_doc_template'], $valid, true ) ) { | ||
| $renderer = $general_settings['single_doc_template']; | ||
|
|
||
| $settings['general']['use_legacy_template'] = 'on'; | ||
| update_option( 'wedocs_settings', $settings ); | ||
|
|
||
| return true; | ||
| // 'elementor' only valid while Elementor is active; otherwise fall through. | ||
| if ( 'elementor' !== $renderer || did_action( 'elementor/loaded' ) ) { | ||
| return apply_filters( 'wedocs_single_doc_renderer', $renderer ); | ||
| } | ||
| } | ||
|
|
||
| if ( wp_is_block_theme() ) { | ||
| // 2. Back-compat with the old binary flag. | ||
| if ( isset( $general_settings['use_legacy_template'] ) ) { | ||
| $renderer = 'on' === $general_settings['use_legacy_template'] ? 'legacy' : 'block'; | ||
|
|
||
| $settings['general']['use_legacy_template'] = 'off'; | ||
| update_option( 'wedocs_settings', $settings ); | ||
| return apply_filters( 'wedocs_single_doc_renderer', $renderer ); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Do not let the binary legacy flag override modern renderer fallback.
The compatibility field collapses both block and elementor into off. If Elementor is later unavailable, the resolver falls through and interprets that value as Block—even on a classic theme—while an unset configuration is also displayed as Block despite PHP auto-detecting another renderer.
includes/functions.php#L1257-L1273: consultuse_legacy_templateonly whensingle_doc_templateis absent; an unavailable explicit renderer should proceed directly to automatic detection.src/components/Settings/GeneralSettings.js#L521-L534: represent an unset renderer as automatic/effective selection rather than defaulting it to Block, and avoid treating the binary field as the source of truth for Elementor.
📍 Affects 2 files
includes/functions.php#L1257-L1273(this comment)src/components/Settings/GeneralSettings.js#L521-L534
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@includes/functions.php` around lines 1257 - 1273, Update the renderer
resolution around the modern setting and legacy fallback in
includes/functions.php: consult use_legacy_template only when
single_doc_template is absent, and let an unavailable explicit renderer continue
directly to automatic detection. In src/components/Settings/GeneralSettings.js,
represent an unset renderer as the automatic/effective selection instead of
defaulting to block, and do not use the binary legacy field as the Elementor
source of truth.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Add duplicate vote prevention using cookies, user metadata, and IP tracking. Fix critical security issue where recipient email was taken from client payload, making the endpoint an open mail relay—now resolved server-side from widget settings. Fix infinite loop in breadcrumb generation. Add proper output escaping and post type validation.
…/wedocs-plugin into elementor-widgets
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Summary by CodeRabbit
Release Notes