Skip to content

Elementor widgets#324

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

Elementor widgets#324
iftakharul-islam wants to merge 15 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 with optional negative feedback
    • Added “Need More Help” support modal with nonce-protected submission (optionally saved to Elementor Pro Forms)
    • Added a documentation search modal with optional ⌘/Ctrl+K shortcut
    • Added new doc widgets: breadcrumb, sidebar, previous/next navigation, table of contents, and hamburger menu
    • Added/expanded Elementor template support for single documentation layouts
  • Style
    • Improved mobile responsiveness for the search modal UI
  • Bug Fixes
    • Improved admin display of helpfulness metrics to show clean numeric counts
  • Improvements
    • Updated previous/next navigation to follow the doc reading order
    • Added a “Single Doc Template” selector (Classic/Block/Elementor) in settings

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

Warning

Review limit reached

@iftakharul-islam, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 59731abc-e0b3-4b4c-8f17-e2fda4e790aa

📥 Commits

Reviewing files that changed from the base of the PR and between 3d178d9 and 68a970e.

📒 Files selected for processing (7)
  • includes/Ajax.php
  • includes/Elementor.php
  • includes/Elementor/Templates/three_column.json
  • includes/Elementor/Widgets/DocsBreadcrumb.php
  • includes/Elementor/Widgets/DocsHamburgerMenu.php
  • includes/Elementor/Widgets/DocsSidebar.php
  • includes/functions.php

Walkthrough

Adds 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.

Changes

Elementor Documentation Experience

Layer / File(s) Summary
Renderer selection and document-tree navigation
includes/functions.php, includes/Templates/SingleDocTemplate.php, src/components/Settings/GeneralSettings.js
Adds centralized classic, block, and Elementor renderer selection, depth-first document navigation, and matching settings UI.
Engagement AJAX endpoints
includes/Ajax.php
Registers helpful-vote, helpful-feedback, and need-help actions that validate requests, update metadata, send email, and optionally store Elementor submissions.
Elementor wiring and template lifecycle
includes/Elementor.php, includes/Elementor/Templates/three_column.json
Registers additional widgets, imports and normalizes Elementor templates, tracks import state, and bundles a three-column single-document template.
Documentation navigation widgets
includes/Elementor/Widgets/DocsSidebar.php, includes/Elementor/Widgets/DocsHamburgerMenu.php, includes/Elementor/Widgets/DocNavigation.php, includes/Elementor/Widgets/DocsBreadcrumb.php, includes/Elementor/Widgets/TableOfContents.php
Adds configurable navigation, breadcrumb, sidebar, hamburger-menu, and table-of-contents widgets with frontend and editor rendering.
Search and engagement widgets
includes/Elementor/Widgets/SearchModal.php, includes/Elementor/Widgets/WasThisHelpful.php, includes/Elementor/Widgets/NeedHelp.php, src/assets/less/responsive.less
Adds search, helpfulness, and support widgets with AJAX behavior, previews, and responsive search-modal styles.
Existing API and admin adjustments
includes/API/API.php, includes/Admin/Docs_List_Table.php
Reformats existing expressions and casts helpfulness metadata to integers before admin output.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: Needs Review

Suggested reviewers: arifulhoque7

Poem

🐰 Widgets hop in line,
Votes and docs align,
Three columns bloom with care,
Search and help float there,
The rabbit cheers the rendered view!

🚥 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 is related to the changes, but it is too generic to convey the main scope of the PR. Use a more specific title such as 'Add Elementor widgets and single-doc templates'.
✅ 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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch elementor-widgets

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.

@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 thread includes/Ajax.php
Comment thread includes/Elementor.php Outdated
Comment thread includes/Elementor.php Outdated
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 thread includes/Elementor/Widgets/DocsBreadcrumb.php Outdated
Comment thread includes/Elementor/Widgets/DocsBreadcrumb.php
Comment thread includes/Elementor/Widgets/DocsHamburgerMenu.php
Comment thread includes/Elementor/Widgets/DocsHamburgerMenu.php Outdated
iftakharul-islam and others added 2 commits June 19, 2026 14:39
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>

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

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 win

Preserve body locking across multiple widget instances.

Closing one hamburger menu unconditionally clears body overflow, 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 win

Add accessible state and names to the off-canvas controls.

Add aria-controls and dynamically updated aria-expanded to 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

📥 Commits

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

📒 Files selected for processing (1)
  • includes/Elementor/Widgets/DocsHamburgerMenu.php

iftakharul-islam and others added 4 commits July 16, 2026 10:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

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

🧹 Nitpick comments (2)
includes/Templates/SingleDocTemplate.php (1)

45-50: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Check 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 lift

Avoid 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

📥 Commits

Reviewing files that changed from the base of the PR and between f188fb8 and 3d178d9.

📒 Files selected for processing (4)
  • includes/Elementor/Widgets/DocNavigation.php
  • includes/Templates/SingleDocTemplate.php
  • includes/functions.php
  • src/components/Settings/GeneralSettings.js

Comment thread includes/functions.php
Comment thread includes/functions.php
Comment on lines +1257 to +1273
// 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 );
}

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.

🎯 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: consult use_legacy_template only when single_doc_template is 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.

iftakharul-islam and others added 5 commits July 16, 2026 10:46
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.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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