Skip to content

Elementor widgets#324

Open
iftakharul-islam wants to merge 4 commits into
developfrom
elementor-widgets
Open

Elementor widgets#324
iftakharul-islam wants to merge 4 commits into
developfrom
elementor-widgets

Conversation

@iftakharul-islam

@iftakharul-islam iftakharul-islam commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Was This Helpful" voting widget for documentation articles with optional feedback collection
    • Added "Need More Help" contact form modal for support requests
    • Added documentation search modal with command-key shortcut support
    • Added breadcrumb, sidebar, navigation, table of contents, and hamburger menu widgets for improved site navigation
    • Added new Elementor template for single documentation page layouts
    • Enhanced admin documentation list with helpfulness metrics display
  • Style

    • Improved responsive design for search modal on mobile devices

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.
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds seven new Elementor widgets (DocsSidebar, DocsHamburgerMenu, DocNavigation, DocsBreadcrumb, TableOfContents, SearchModal, WasThisHelpful, NeedHelp), three new AJAX handlers for voting/feedback/contact, a full Elementor template-import lifecycle with a bundled three-column JSON template, responsive CSS for the search modal, and minor formatting/type-cast fixes in existing admin and API files.

Changes

Elementor Widgets, AJAX Handlers & Template Lifecycle

Layer / File(s) Summary
AJAX endpoints: voting, feedback, need-help, Elementor submissions
includes/Ajax.php
Constructor registers five new wp_ajax actions. handle_helpful_vote() validates and increments post-meta counters. handle_helpful_feedback() appends timestamped records to a meta array. handle_need_help_submit() validates, emails, and optionally saves to Elementor Pro. save_to_elementor_submissions() maps fields into Elementor Pro submission payloads.
Elementor class: hook wiring, widget registration, template import lifecycle
includes/Elementor.php
Constructor wires additional hooks for script/style registration, widget categories, and template initialization. register_widgets() adds an elementor/loaded guard and requires/registers all new widget classes. A new template-import subsystem reads JSON files from includes/Elementor/Templates/, inserts them as elementor_library posts via wp_insert_post(), normalizes meta formats, tracks import state via an option flag, and enqueues an inline editor-refresh script.
Navigation/sidebar widgets: DocsSidebar, DocsHamburgerMenu, DocNavigation, DocsBreadcrumb
includes/Elementor/Widgets/DocsSidebar.php, includes/Elementor/Widgets/DocsHamburgerMenu.php, includes/Elementor/Widgets/DocNavigation.php, includes/Elementor/Widgets/DocsBreadcrumb.php
Four widgets covering hierarchical sidebar with caret/mobile modes, off-canvas hamburger panel, prev/next sibling navigation (via $wpdb), and schema.org breadcrumb trail. Each implements register_controls(), render() with inline CSS/JS, content_template(), and render_editor_preview().
Content/discovery widgets: TableOfContents, SearchModal + responsive CSS
includes/Elementor/Widgets/TableOfContents.php, includes/Elementor/Widgets/SearchModal.php, src/assets/less/responsive.less
TableOfContents extracts heading levels from post content and builds a sticky/collapsible TOC with a jQuery runtime that rebuilds from live DOM. SearchModal renders a full-screen AJAX search modal loading docs via wedocs_get_docs, filtering by title, and rendering up to 20 results. Responsive LESS rules adjust the modal layout at 768px and 425px.
Engagement widgets: WasThisHelpful, NeedHelp
includes/Elementor/Widgets/WasThisHelpful.php, includes/Elementor/Widgets/NeedHelp.php
WasThisHelpful renders thumbs/emoji/yes-no voting that posts to wedocs_helpful_vote/wedocs_helpful_feedback AJAX actions and transitions UI to a thank-you state. NeedHelp renders a card/inline/minimal trigger opening a nonce-protected modal form that submits via wedocs_need_help_submit. Both embed inline CSS and jQuery logic.
Three-column template JSON and minor formatting fixes
includes/Elementor/Templates/three_column.json, includes/API/API.php, includes/Admin/Docs_List_Table.php
three_column.json defines a bundled Elementor single-post template with left sidebar, center content (title/breadcrumb/content/WasThisHelpful/NeedHelp), and right TOC column. API.php receives get_post_meta argument spacing and a whitespace fix. Docs_List_Table.php adds (int) casts for positive/negative meta before output and reformats hook/array literals.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • weDevsOfficial/wedocs-plugin#209: Introduces the promotional-notice REST routes and related actions in includes/API/API.php at the same functions touched by the whitespace/formatting fix in this PR.
  • weDevsOfficial/wedocs-plugin#213: Modifies includes/Elementor.php to expand Elementor widget wiring/initialization, which is the same file substantially rewritten in this PR.
  • weDevsOfficial/wedocs-plugin#239: Introduces the single-doc search modal AJAX and DOM logic that this PR extends with the SearchModal Elementor widget and responsive CSS targeting #wedocs-single-doc-search-modal.

Suggested labels

Needs Review

Suggested reviewers

  • arifulhoque7

Poem

🐰 Hops through seven widgets, ears up high,
AJAX handlers catching votes that fly,
Templates imported, columns three align,
Breadcrumbs and sidebars — oh how they shine!
A TOC built from headings in the page,
The rabbit ships it all in one big stage! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Elementor widgets' is vague and overly broad. While the PR does add multiple Elementor widgets, the title fails to convey the specific purpose, context, or scope of the changes (e.g., it doesn't indicate whether this is adding new widgets, refactoring existing ones, or integrating Elementor support). Use a more descriptive title that captures the primary intent, such as 'Add Elementor widgets for documentation display' or 'Integrate Elementor widgets with AJAX handlers' to clarify the main contribution.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch elementor-widgets

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PHPStan (2.2.2)

PHP Fatal error: Uncaught Error: Undefined constant "ABSPATH" in /includes/functions.php:423
Stack trace:
#0 /includes/functions.php(397): wedocs_is_plugin_active()
#1 /vendor/composer/autoload_real.php(39): require('...')
#2 /vendor/composer/autoload_real.php(43): {closure}()
#3 /vendor/autoload.php(25): ComposerAutoloaderInit5f05af6f4b83f2cc1522ec0dec039234::getLoader()
#4 phar:///usr/bin/phpstan/bin/phpstan(46): require_once('...')
#5 phar:///usr/bin/phpstan/bin/phpstan(107): _PHPStan_2874a496b{closure}()
#6 /usr/bin/phpstan(7): require('...')
#7 {main}
thrown in /includes/functions.php on line 423
Fatal error: Uncaught Error: Undefined constant "ABSPATH" in /includes/functions.php:423
Stack trace:
#0 /includes/functions.php(397): wedocs_is_plugin_active()
#1 /vendor/composer/autoload_real.php(39): require('...')
#2 /vendor/composer/autoload_real.php(43): {closure}()
#3 /vendor/autoload.php(25): ComposerAutoloaderInit5f05af6f4b83f2cc1522ec0dec039234::getLoader()
#4 phar:///usr/bin/phpstan/bin/phpstan(46): require_once('...')
#5 phar:///usr/bin/phpstan/bin/phpstan(107): _PHPStan_2874a496b{closure}()
#6 /usr/bin/phpstan(7): require('...')
#7 {main}
thrown in /includes/functions.php on line 423


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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (2)
includes/Elementor.php (1)

117-120: ⚡ Quick win

Avoid normalizing template meta on every request.

fix_existing_template_conditions() runs before the import flag check, so every init performs an unbounded elementor_library query 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 value

Consider 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() returns false, which becomes the string 'false' in JavaScript. This is sent to the server as post_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

📥 Commits

Reviewing files that changed from the base of the PR and between f035321 and d9f96a3.

📒 Files selected for processing (14)
  • includes/API/API.php
  • includes/Admin/Docs_List_Table.php
  • includes/Ajax.php
  • includes/Elementor.php
  • includes/Elementor/Templates/three_column.json
  • includes/Elementor/Widgets/DocNavigation.php
  • includes/Elementor/Widgets/DocsBreadcrumb.php
  • includes/Elementor/Widgets/DocsHamburgerMenu.php
  • includes/Elementor/Widgets/DocsSidebar.php
  • includes/Elementor/Widgets/NeedHelp.php
  • includes/Elementor/Widgets/SearchModal.php
  • includes/Elementor/Widgets/TableOfContents.php
  • includes/Elementor/Widgets/WasThisHelpful.php
  • src/assets/less/responsive.less

Comment thread includes/Ajax.php
Comment on lines +593 to +611
public function handle_helpful_vote() {
check_ajax_referer('wedocs_helpful_vote', 'nonce');

$post_id = intval($_POST['post_id'] ?? 0);
$vote = sanitize_text_field($_POST['vote'] ?? '');

if (!$post_id || !in_array($vote, ['yes', 'no'], true)) {
wp_send_json_error(['message' => __('Invalid vote.', 'wedocs')]);
}

$meta_key = $vote === 'yes' ? 'positive' : 'negative';
$current = (int) get_post_meta($post_id, $meta_key, true);
update_post_meta($post_id, $meta_key, $current + 1);

wp_send_json_success([
'yes' => (int) get_post_meta($post_id, 'positive', true),
'no' => (int) get_post_meta($post_id, 'negative', true),
]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing duplicate vote prevention allows unlimited vote manipulation.

Unlike the existing handle_helpful_feedback_vote() method (lines 434-481) which implements cookie-based tracking, user meta, and IP meta checks to prevent duplicate votes, this handler has no such protection. Any user can call this endpoint repeatedly to artificially inflate positive or negative counts.

Additionally, there's no validation that $post_id refers to a docs post type (compare to line 417 in the existing handler).

🔒 Proposed fix: Add duplicate vote prevention
 public function handle_helpful_vote() {
     check_ajax_referer('wedocs_helpful_vote', 'nonce');

     $post_id = intval($_POST['post_id'] ?? 0);
     $vote = sanitize_text_field($_POST['vote'] ?? '');

     if (!$post_id || !in_array($vote, ['yes', 'no'], true)) {
         wp_send_json_error(['message' => __('Invalid vote.', 'wedocs')]);
     }

+    // Verify this is a docs post
+    if (get_post_type($post_id) !== 'docs') {
+        wp_send_json_error(['message' => __('Invalid post type.', 'wedocs')]);
+    }
+
+    // Check for duplicate vote via cookie
+    $previous = isset($_COOKIE['wedocs_response']) ? explode(',', $_COOKIE['wedocs_response']) : [];
+    if (in_array($post_id, $previous, false)) {
+        wp_send_json_error([
+            'already_voted' => true,
+            'message' => __('You have already voted on this article.', 'wedocs'),
+        ]);
+    }
+
     $meta_key = $vote === 'yes' ? 'positive' : 'negative';
     $current = (int) get_post_meta($post_id, $meta_key, true);
     update_post_meta($post_id, $meta_key, $current + 1);

+    // Set cookie to prevent duplicate votes
+    $previous[] = $post_id;
+    setcookie('wedocs_response', implode(',', $previous), time() + WEEK_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN);
+
     wp_send_json_success([
         'yes' => (int) get_post_meta($post_id, 'positive', true),
         'no' => (int) get_post_meta($post_id, 'negative', true),
     ]);
 }
🤖 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/Ajax.php` around lines 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.

Comment thread includes/Ajax.php
Comment on lines +616 to +639
public function handle_helpful_feedback() {
check_ajax_referer('wedocs_helpful_vote', 'nonce');

$post_id = intval($_POST['post_id'] ?? 0);
$feedback = sanitize_textarea_field($_POST['feedback'] ?? '');

if (!$post_id || empty($feedback)) {
wp_send_json_error(['message' => __('Invalid feedback.', 'wedocs')]);
}

$existing = get_post_meta($post_id, '_wedocs_helpful_feedback', true);
if (!is_array($existing)) {
$existing = [];
}

$existing[] = [
'feedback' => $feedback,
'date' => current_time('mysql'),
'ip' => sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? ''),
];

update_post_meta($post_id, '_wedocs_helpful_feedback', $existing);
wp_send_json_success();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add post type validation to prevent feedback on non-docs posts.

Similar to handle_helpful_vote(), this handler accepts any valid post ID without verifying it's a docs post type.

🛡️ Proposed fix
     $post_id = intval($_POST['post_id'] ?? 0);
     $feedback = sanitize_textarea_field($_POST['feedback'] ?? '');

     if (!$post_id || empty($feedback)) {
         wp_send_json_error(['message' => __('Invalid feedback.', 'wedocs')]);
     }

+    if (get_post_type($post_id) !== 'docs') {
+        wp_send_json_error(['message' => __('Invalid post type.', 'wedocs')]);
+    }
+
     $existing = get_post_meta($post_id, '_wedocs_helpful_feedback', true);
🤖 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/Ajax.php` around lines 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.

Comment thread includes/Elementor.php
Comment on lines +139 to +151
// Check if template import is needed
if (get_option('wedocs_elementor_templates_imported')) {
return;
}

$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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the one-time import atomic.

The get_option()wp_insert_post()update_option() sequence has a race window: concurrent first requests can both insert the default template and attach the same conditions. Use an add_option() lock/claim or transient lock, then re-check before inserting.

Suggested locking shape
         // Check if template import is needed
         if (get_option('wedocs_elementor_templates_imported')) {
             return;
         }
 
-        $template_id = $this->import_template_from_json('three_column', 'weDocs - Single Doc Page');
+        if (! add_option('wedocs_elementor_templates_import_lock', time(), '', 'no')) {
+            return;
+        }
+
+        try {
+            $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');
+            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);
+                // Mark as imported
+                update_option('wedocs_elementor_templates_imported', true);
+            }
+        } finally {
+            delete_option('wedocs_elementor_templates_import_lock');
         }
📝 Committable suggestion

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

Suggested change
// Check if template import is needed
if (get_option('wedocs_elementor_templates_imported')) {
return;
}
$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);
// Check if template import is needed
if (get_option('wedocs_elementor_templates_imported')) {
return;
}
if (! add_option('wedocs_elementor_templates_import_lock', time(), '', 'no')) {
return;
}
try {
$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);
}
} finally {
delete_option('wedocs_elementor_templates_import_lock');
}
🤖 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 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.

Comment thread includes/Elementor.php
Comment on lines +144 to +151
$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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "Elementor.php" -type f

Repository: weDevsOfficial/wedocs-plugin

Length of output: 97


🏁 Script executed:

wc -l includes/Elementor.php

Repository: weDevsOfficial/wedocs-plugin

Length of output: 99


🏁 Script executed:

sed -n '140,160p' includes/Elementor.php

Repository: weDevsOfficial/wedocs-plugin

Length of output: 762


🏁 Script executed:

sed -n '260,280p' includes/Elementor.php

Repository: 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 -80

Repository: weDevsOfficial/wedocs-plugin

Length of output: 2566


🏁 Script executed:

grep -n "function set_template_conditions\|function import_template_from_json" includes/Elementor.php -A 30

Repository: 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:


🌐 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:


🏁 Script executed:

grep -r "elementor_conditions\|theme.*builder\|precedence" includes/ --include="*.php" | head -20

Repository: 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.

Comment thread includes/Elementor.php
Comment on lines +203 to +207
// 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));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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=php

Repository: 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 -B2

Repository: 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 -i

Repository: 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 -A3

Repository: 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:


🏁 Script executed:

# Check the context around line 270 to see the full wp_insert_post call
sed -n '260,280p' includes/Elementor.php

Repository: 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:


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_input array in wp_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.

Suggested change
// 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.

Comment on lines +400 to +413
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines +377 to +384
while ($parent_id) {
$page = get_post($parent_id);
$parents[] = [
'label' => get_the_title($page->ID),
'url' => get_permalink($page->ID),
];
$parent_id = $page->post_parent;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle missing parent posts in breadcrumb traversal.

Line 378 can return null from get_post($parent_id), but Line 380/383 dereference $page unguarded. An orphaned post_parent will break this render path.

Suggested fix
             while ($parent_id) {
                 $page = get_post($parent_id);
+                if ( ! $page ) {
+                    break;
+                }
                 $parents[] = [
                     'label' => get_the_title($page->ID),
                     'url'   => get_permalink($page->ID),
                 ];
                 $parent_id = $page->post_parent;
             }
📝 Committable suggestion

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

Suggested change
while ($parent_id) {
$page = get_post($parent_id);
$parents[] = [
'label' => get_the_title($page->ID),
'url' => get_permalink($page->ID),
];
$parent_id = $page->post_parent;
}
while ($parent_id) {
$page = get_post($parent_id);
if ( ! $page ) {
break;
}
$parents[] = [
'label' => get_the_title($page->ID),
'url' => get_permalink($page->ID),
];
$parent_id = $page->post_parent;
}
🤖 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/DocsBreadcrumb.php` around lines 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().

Comment on lines +390 to +393
$items[] = [
'label' => get_the_title(),
'current' => true,
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the resolved docs context for the current breadcrumb label.

Line 391 calls get_the_title() with no ID, which relies on global post state. In editor/fallback contexts this can produce the wrong current label.

Suggested fix
-        $items[] = [
-            'label'   => get_the_title(),
+        $items[] = [
+            'label'   => get_the_title( $current_post->ID ),
             'current' => true,
         ];
📝 Committable suggestion

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

Suggested change
$items[] = [
'label' => get_the_title(),
'current' => true,
];
$items[] = [
'label' => get_the_title( $current_post->ID ),
'current' => true,
];
🤖 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/DocsBreadcrumb.php` around lines 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.

Comment on lines +404 to +416
global $post;

// Determine the parent doc
$ancestors = [];
$parent = false;

if (!empty($post->post_parent)) {
$ancestors = get_post_ancestors($post->ID);
$root = count($ancestors) - 1;
$parent = $ancestors[$root];
} else {
$parent = !empty($post->ID) ? $post->ID : '';
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard $post before dereferencing doc fields.

Line 410 and Line 415 access $post properties without confirming $post is a valid WP_Post. This widget can render outside single-doc contexts, so this path can emit runtime warnings and break rendering.

Suggested fix
-        global $post;
+        global $post;
+        if ( ! ( $post instanceof \WP_Post ) || $post->post_type !== 'docs' ) {
+            if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
+                echo '<p style="color: `#999`; font-style: italic; padding: 20px; text-align: center;">' . esc_html__( 'Hamburger Menu: Preview it on a single doc page.', 'wedocs' ) . '</p>';
+            }
+            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.

Suggested change
global $post;
// Determine the parent doc
$ancestors = [];
$parent = false;
if (!empty($post->post_parent)) {
$ancestors = get_post_ancestors($post->ID);
$root = count($ancestors) - 1;
$parent = $ancestors[$root];
} else {
$parent = !empty($post->ID) ? $post->ID : '';
}
global $post;
if ( ! ( $post instanceof \WP_Post ) || $post->post_type !== 'docs' ) {
if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
echo '<p style="color: `#999`; font-style: italic; padding: 20px; text-align: center;">' . esc_html__( 'Hamburger Menu: Preview it on a single doc page.', 'wedocs' ) . '</p>';
}
return;
}
// Determine the parent doc
$ancestors = [];
$parent = false;
if (!empty($post->post_parent)) {
$ancestors = get_post_ancestors($post->ID);
$root = count($ancestors) - 1;
$parent = $ancestors[$root];
} else {
$parent = !empty($post->ID) ? $post->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/DocsHamburgerMenu.php` around lines 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.

Comment on lines +458 to +460
<a href="<?php echo get_permalink($parent); ?>" style="text-decoration: none; color: inherit;">
<?php echo get_post_field('post_title', $parent, 'display'); ?>
</a>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Escape permalink and title in the panel header link.

Line 458 outputs get_permalink() raw and Line 459 outputs post_title raw. These should be escaped for URL/text output contexts.

Suggested fix
-                        <a href="<?php echo get_permalink($parent); ?>" style="text-decoration: none; color: inherit;">
-                            <?php echo get_post_field('post_title', $parent, 'display'); ?>
+                        <a href="<?php echo esc_url( get_permalink( $parent ) ); ?>" style="text-decoration: none; color: inherit;">
+                            <?php echo esc_html( get_post_field( 'post_title', $parent, 'display' ) ); ?>
                         </a>
📝 Committable suggestion

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

Suggested change
<a href="<?php echo get_permalink($parent); ?>" style="text-decoration: none; color: inherit;">
<?php echo get_post_field('post_title', $parent, 'display'); ?>
</a>
<a href="<?php echo esc_url( get_permalink( $parent ) ); ?>" style="text-decoration: none; color: inherit;">
<?php echo esc_html( get_post_field( 'post_title', $parent, 'display' ) ); ?>
</a>
🤖 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 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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant