Skip to content

chore: WordPress.org compliance audit, SEO readme, and PCP fixes#4

Merged
Jmosier69 merged 8 commits intomainfrom
emdash/feat-plugin-httpswordpress-5qg
Apr 10, 2026
Merged

chore: WordPress.org compliance audit, SEO readme, and PCP fixes#4
Jmosier69 merged 8 commits intomainfrom
emdash/feat-plugin-httpswordpress-5qg

Conversation

@Jmosier69
Copy link
Copy Markdown
Contributor

@Jmosier69 Jmosier69 commented Apr 10, 2026

Summary

Prepares the plugin for WordPress.org directory submission by addressing all 18 Plugin Directory Guidelines, merging the SEO-optimized readme.txt, and fixing every error reported by the Plugin Check Plugin (PCP).

  • readme.txt: Full rewrite with SEO-optimized title/description/FAQ, expanded External Services section listing every external domain individually with Terms/Privacy links, screenshots section, updated pricing
  • PCP fixes: GA4 script now uses wp_enqueue_script(), multiline pixel echoes collapsed so phpcs:ignore covers escaped variables, $_POST JSON input annotated
  • Compliance: Remove Ads Advisor references, update call tracking rates to new pricing ($8/$6/$4), bump Tested up to 6.9, fix notice dismissal persistence, add iframe architectural comment for reviewers
  • Plugin header: Plugin Name and Description synced between roi-insights.php and readme.txt, added Tested up to field

Files changed

File What changed
readme.txt Full rewrite — SEO package + compliance External Services section
roi-insights.php Plugin Name, Description, Tested up to synced with readme
README.md Remove Ads Advisor, update call tracking rates and cost tables
includes/class-tracking.php GA4 wp_enqueue_script(), collapse multiline pixel echoes
includes/class-api.php phpcs annotation for JSON $_POST input
includes/class-roi-insights.php Notice dismissal AJAX handler + persistence
src/admin/Dashboard.tsx Iframe architectural comment for WP.org reviewers

PCP results addressed

Error Resolution
Tested up to 6.7 < 6.9 Bumped to 6.9
NonEnqueuedScript (GA4) wp_enqueue_script()
EscapeOutput (TikTok/Pinterest/Nextdoor) Collapsed to single-line echoes
InputNotSanitized ($_POST) phpcs:ignore with explanation
Text domain / slug mismatch False positive from test folder name — resolves at submission

Test plan

  • Verify all tracking pixels still render correctly on frontend pages
  • Confirm GA4 gtag.js loads via wp_enqueue_script (check page source for <script> tag in expected position)
  • Test notice dismissal persists across page loads (set an invalid license key, dismiss notice, reload)
  • Validate readme.txt at https://wordpress.org/plugins/developers/readme-validator/
  • Re-run Plugin Check Plugin with plugin installed as roi-insights/ folder

Note

Medium Risk
Moderate risk because it changes frontend script loading (GA4 via wp_enqueue_script) and adds a new admin AJAX endpoint for persisting notice dismissal; both could affect tracking behavior or admin UX if misconfigured.

Overview
Prepares the plugin for WordPress.org submission by rewriting readme.txt (SEO/content refresh plus detailed External Services disclosures) and syncing plugin metadata in roi-insights.php (name/description, WP minimum and tested versions).

Addresses Plugin Check/PHPCS issues by switching GA4 loading to wp_enqueue_script, tightening $_POST JSON handling annotations, collapsing multi-line pixel script echoes, and adding a packaging script (plugin-zip) plus ignoring the generated roi-insights.zip.

Improves license UX by persisting dismissal of the invalid-license admin notice via a new wp_ajax_roi_insights_dismiss_notice handler, and resetting dismissal state when the license cache is cleared; documentation/pricing tables are also updated (new call tracking rates and removal of Ads Advisor references).

Reviewed by Cursor Bugbot for commit 7a1a451. Bugbot is set up for automated code reviews on this repo. Configure here.

Prepare the plugin for WordPress.org submission by addressing all 18
Plugin Directory Guidelines and fixing every error from the Plugin
Check Plugin (PCP).

Compliance & readme:
- Rewrite readme.txt with SEO-optimized title, description, FAQ,
  screenshots section, and expanded External Services documentation
  listing every external domain individually with Terms/Privacy links
- Update Plugin Name header in roi-insights.php to match readme title
- Bump Tested up to from 6.7 to 6.9 (current WordPress release)
- Remove all Ads Advisor references from readme.txt and README.md
- Update call tracking rates to new pricing ($8/$6/$4 per number)
- Add call recording credit rates to tier descriptions

PCP fixes:
- Replace raw GA4 <script src> tag with wp_enqueue_script()
- Collapse multiline pixel echoes (TikTok, Pinterest, Nextdoor) to
  single lines so phpcs:ignore directives cover the escaped variables
- Add phpcs:ignore annotation for $_POST JSON input with explanation

Admin UX:
- Fix notice dismissal: add AJAX handler to persist dismissed state
  in user_meta so is-dismissible notices stay hidden across page loads
- Add code comment on Dashboard iframe explaining architectural choice
  (WordPress.org Guideline 8 — reviewer documentation)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Updated documentation and plugin metadata; enqueued GA4 loader instead of inline script; added authenticated AJAX endpoint and admin JS to persist license-notice dismissal; expanded license cache clearing to remove related user-meta; tightened PHPCS annotation; added an admin iframe comment.

Changes

Cohort / File(s) Summary
Documentation & Plugin Header
README.md, readme.txt, roi-insights.php
Revised README pricing/features (call-tracking rates, recording credit limits, removed Ads Advisor), expanded readme.txt description/FAQ/features, and updated plugin header metadata (Plugin Name, Requires at least: 6.3, Tested up to: 6.9).
License Notice Dismissal
includes/class-roi-insights.php
Added wp_ajax_roi_insights_dismiss_notice hook and public handle_dismiss_notice(); notice markup includes stable DOM id and per-render nonce; injected admin JS intercepts dismiss click and POSTs to admin-ajax.php; server verifies nonce & capability and updates roi_insights_license_notice_dismissed user meta.
License Cache Clear
includes/class-license.php
ROI_Insights_License::clear_cache() now deletes self::CACHE_KEY . '_stale' transient and removes roi_insights_license_notice_dismissed user meta for current user in addition to existing transient clear.
Tracking Script Output
includes/class-tracking.php
GA4 gtag loader moved from inline echo to wp_enqueue_script( 'roi-insights-ga4-gtag', ... , ['strategy' => 'async'] ); inline gtag('js'...)/gtag('config'...) still echoed; other vendor script echoes reformatted without behavior change.
API Security Annotation
includes/class-api.php
Expanded PHPCS ignore annotation on get_body() to also suppress WordPress.Security.ValidatedSanitizedInput.InputNotSanitized because POST data is treated as raw JSON after prior nonce verification.
Admin Dashboard Comment
src/admin/Dashboard.tsx
Added an inline comment before the embedded <iframe> explaining sandboxing, short-lived session token auth, and active-tab gating; no runtime changes.

Sequence Diagram

sequenceDiagram
    participant Admin as Admin User
    participant JS as Admin JS (notice)
    participant Ajax as admin-ajax.php
    participant PHP as ROI_Insights::handle_dismiss_notice
    participant Meta as User Meta

    Admin->>JS: Click dismiss on license notice
    JS->>JS: Intercept click, gather nonce/action
    JS->>Ajax: POST action=roi_insights_dismiss_notice, nonce
    Ajax->>PHP: Route request
    PHP->>PHP: wp_verify_nonce()
    PHP->>PHP: current_user_can('manage_options')
    PHP->>Meta: update_user_meta(user_id, 'roi_insights_license_notice_dismissed', true)
    Meta-->>PHP: Success
    PHP-->>Ajax: JSON { "success": true }
    Ajax-->>JS: Success response
    JS->>JS: Remove/hide notice in DOM
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Poem

🐰 I nudged a nonce with a twitching paw,

I queued a script and trimmed the doc’s maw,
Notices hushed and cached crumbs swept light,
Tiny hops of code in the quiet night,
A rabbit’s patch—soft, swift, and bright.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main focus of the pull request: WordPress.org compliance, SEO improvements to readme.txt, and Plugin Check Plugin fixes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request description directly addresses the changeset by documenting WordPress.org compliance updates, PCP fixes, readme.txt rewrite, and plugin header syncing across all modified files.

✏️ 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 emdash/feat-plugin-httpswordpress-5qg

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
includes/class-roi-insights.php (1)

221-230: Inline XHR lacks error handling and may silently fail.

The inline script fires the dismiss request but doesn't handle network errors or non-2xx responses. If the request fails, the user won't know, and the notice will reappear on the next page load.

♻️ Suggested improvement with minimal error handling
-		echo '<script>document.addEventListener("DOMContentLoaded",function(){var n=document.getElementById("roi-insights-license-notice");if(n){n.addEventListener("click",function(e){if(e.target.classList.contains("notice-dismiss")){var x=new XMLHttpRequest();x.open("POST","' . esc_url( admin_url( 'admin-ajax.php' ) ) . '");x.setRequestHeader("Content-Type","application/x-www-form-urlencoded");x.send("action=roi_insights_dismiss_notice&_wpnonce="+n.dataset.nonce)}})}});</script>' . "\n";
+		echo '<script>document.addEventListener("DOMContentLoaded",function(){var n=document.getElementById("roi-insights-license-notice");if(n){n.addEventListener("click",function(e){if(e.target.classList.contains("notice-dismiss")){var x=new XMLHttpRequest();x.open("POST","' . esc_url( admin_url( 'admin-ajax.php' ) ) . '");x.setRequestHeader("Content-Type","application/x-www-form-urlencoded");x.onerror=function(){console.warn("ROI Insights: Failed to persist notice dismissal")};x.send("action=roi_insights_dismiss_notice&_wpnonce="+n.dataset.nonce)}})}});</script>' . "\n";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/class-roi-insights.php` around lines 221 - 230, The inline XHR
attached to the roi-insights-license-notice (action roi_insights_dismiss_notice)
lacks error handling and can silently fail; update the script that sends the
POST to admin-ajax.php to add proper XHR handlers: set x.onerror to log/report
network errors, set x.onload to check x.status for 2xx and handle non-2xx
responses (log or retry), and optionally provide a visual fallback (e.g., keep
the notice visible or show a brief message) so failures are surfaced; modify the
inline script block that creates XMLHttpRequest in
includes/class-roi-insights.php to wire those handlers and ensure
n.dataset.nonce is escaped/used safely.
includes/class-tracking.php (1)

91-92: Calling wp_enqueue_script() inside wp_head is unconventional.

Scripts are typically enqueued via the wp_enqueue_scripts hook. However, since inject_head executes at priority 1 and wp_print_head_scripts runs at priority 9 within wp_head, the GA4 script will be queued before it's printed. This should work correctly, but testing in production would confirm the script loads as expected in the final <head> output.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@includes/class-tracking.php` around lines 91 - 92, The GA4 script is being
enqueued directly inside inject_head which is unconventional; move the
wp_enqueue_script call for 'roi-insights-ga4-gtag' out of inject_head and into a
proper enqueue hook (e.g., hook into wp_enqueue_scripts or admin_enqueue_scripts
as appropriate) so scripts are registered/enqueued in the canonical phase, then
leave inject_head to only output the inline gtag bootstrap (or use
wp_add_inline_script tied to 'roi-insights-ga4-gtag'); update references to
rawurlencode($s['ga4Id'])/ $ga4_id accordingly and ensure the inline script is
escaped or attached via wp_add_inline_script rather than echoing directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@includes/class-roi-insights.php`:
- Around line 221-230: The inline XHR attached to the
roi-insights-license-notice (action roi_insights_dismiss_notice) lacks error
handling and can silently fail; update the script that sends the POST to
admin-ajax.php to add proper XHR handlers: set x.onerror to log/report network
errors, set x.onload to check x.status for 2xx and handle non-2xx responses (log
or retry), and optionally provide a visual fallback (e.g., keep the notice
visible or show a brief message) so failures are surfaced; modify the inline
script block that creates XMLHttpRequest in includes/class-roi-insights.php to
wire those handlers and ensure n.dataset.nonce is escaped/used safely.

In `@includes/class-tracking.php`:
- Around line 91-92: The GA4 script is being enqueued directly inside
inject_head which is unconventional; move the wp_enqueue_script call for
'roi-insights-ga4-gtag' out of inject_head and into a proper enqueue hook (e.g.,
hook into wp_enqueue_scripts or admin_enqueue_scripts as appropriate) so scripts
are registered/enqueued in the canonical phase, then leave inject_head to only
output the inline gtag bootstrap (or use wp_add_inline_script tied to
'roi-insights-ga4-gtag'); update references to rawurlencode($s['ga4Id'])/
$ga4_id accordingly and ensure the inline script is escaped or attached via
wp_add_inline_script rather than echoing directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed56f24e-a7b1-4ff4-988e-ac04957a6427

📥 Commits

Reviewing files that changed from the base of the PR and between e187330 and 1ae796a.

📒 Files selected for processing (7)
  • README.md
  • includes/class-api.php
  • includes/class-roi-insights.php
  • includes/class-tracking.php
  • readme.txt
  • roi-insights.php
  • src/admin/Dashboard.tsx

Comment thread includes/class-tracking.php Outdated
Comment thread includes/class-roi-insights.php
- Clear roi_insights_license_notice_dismissed user meta in
  clear_cache() so license warnings reappear after key changes
  or expiration instead of staying permanently hidden.

- Use WP 6.3+ script loading strategy array for GA4 gtag.js
  enqueue to preserve async loading behavior from the original
  raw script tag.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/class-license.php`:
- Around line 269-270: clear_cache() currently deletes self::CACHE_KEY transient
and a user meta but doesn't remove the stale flag, so stale_or_invalid() may
return old data after transient backend failures; update clear_cache() to also
delete the 'roi_insights_license_cache_stale' transient (or corresponding
constant) so that delete_transient(self::CACHE_KEY) is followed by
delete_transient('roi_insights_license_cache_stale') (and keep
delete_user_meta(...) unchanged) to ensure stale_or_invalid() can't read stale
cache after a key update.
🪄 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: 46f86542-8fee-4034-900f-5e2bdbedf375

📥 Commits

Reviewing files that changed from the base of the PR and between 1ae796a and 4818978.

📒 Files selected for processing (2)
  • includes/class-license.php
  • includes/class-tracking.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • includes/class-tracking.php

Comment thread includes/class-license.php
Delete the _stale transient in clear_cache() so stale_or_invalid()
cannot return data from a previous license key if the backend is
temporarily unreachable after a key update.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@includes/class-license.php`:
- Around line 265-271: The clear_cache() method currently deletes the
'roi_insights_license_notice_dismissed' meta only for get_current_user_id(),
which leaves other users' dismissals intact; either change the implementation to
clear the meta for all users (e.g., use delete_metadata('user', 0,
'roi_insights_license_notice_dismissed') or run a WP_User_Query and loop
delete_user_meta for each user) so that CACHE_KEY invalidation truly surfaces
notices to all admins, or update the clear_cache() docstring to explicitly state
it only resets the dismissal for the current user; adjust the code in
clear_cache() and mention the CACHE_KEY and
'roi_insights_license_notice_dismissed' keys accordingly.
🪄 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: a39bfee5-469d-448a-873c-9de3e0b8ae40

📥 Commits

Reviewing files that changed from the base of the PR and between 4818978 and 2f6c482.

📒 Files selected for processing (1)
  • includes/class-license.php

Comment thread includes/class-license.php
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2f6c482. Configure here.

Comment thread includes/class-tracking.php
The wp_enqueue_script() array args format (strategy => async) was
introduced in WP 6.3. Bump Requires at least from 6.2 to 6.3 so
the GA4 async enqueue works correctly. WP 6.2 has been EOL since
November 2024.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@readme.txt`:
- Line 91: Update the FAQ sentence that currently reads “All tracking scripts
load asynchronously and do not block page rendering” to a toned-down claim:
replace or rephrase that exact sentence to acknowledge exceptions (e.g., mention
that remote scripts are loaded async but small inline bootstrap snippets may run
on the main thread) and keep the note about md-roi.js size; ensure the revised
copy preserves the meaning of asynchronous loading while avoiding an absolute
guarantee.
🪄 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: 43002e3b-6a24-4071-affc-ef3db5af5c5c

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6c482 and f1acee3.

📒 Files selected for processing (2)
  • readme.txt
  • roi-insights.php
✅ Files skipped from review due to trivial changes (1)
  • roi-insights.php

Comment thread readme.txt Outdated
Acknowledge that inline pixel bootstrap snippets run on the main
thread, while remote scripts are loaded async.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@readme.txt`:
- Line 128: The readme statement is misleading because the local attribution
script is enqueued unconditionally; update the wording to clarify that while no
external network requests are made until license activation, Google connect, or
tracking pixel enablement, the plugin still loads a local tracker
file—md-roi.js—on all frontend pages via the enqueue_tracker() method in
includes/class-tracking.php (hooked to wp_enqueue_scripts); change the sentence
to explicitly state that md-roi.js is loaded locally before activation or pixel
enablement, or alternatively adjust enqueue_tracker() to conditionally enqueue
the script only after activation if you prefer that behavior.
- Line 36: The README advertises a "Basic call log" feature that isn't
implemented; either remove that claim or implement it: to implement, add call
storage and schema (e.g., new DB table and model), add retrieval endpoints in
class-api.php (e.g., GET /calls and GET /calls/:id) that fetch from
api.roiknowledge.com if applicable, add admin settings toggles in
class-settings.php and settings UI for enabling call logging, and implement an
admin dashboard view to display calls (source, caller ID, duration) and
pagination; if opting to remove the claim, update the readme to delete the
"Basic call log" bullet and any references to dniScriptUrl/dniSwapNumber that
imply call logging. Ensure code touches reference dniScriptUrl and dniSwapNumber
only if you implement DNI-based call collection and document the backend
endpoint used for retrieval.
🪄 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: ea2227bd-d754-4329-b030-0be6a0a63f06

📥 Commits

Reviewing files that changed from the base of the PR and between f1acee3 and 5eb11ae.

📒 Files selected for processing (1)
  • readme.txt

Comment thread readme.txt Outdated
Comment thread readme.txt Outdated
Jim Mosier added 3 commits April 10, 2026 15:54
PCP flags both .Missing and .Recommended variants for $_POST access.
The nonce is verified in verify_request() which is called before
get_body() in every handler.
npm run plugin-zip builds the frontend, assembles only the runtime
files into a roi-insights/ directory, zips it, and cleans up.

Excludes: .git, .gitignore, node_modules, src, tsconfig.json,
package.json, package-lock.json, README.md (GitHub-only).
- Remove "Basic call log" as a standalone free feature bullet — the
  call log is part of the embedded dashboard service, not a plugin
  feature. Reword to make this clear.
- Clarify that md-roi.js loads on all frontend pages as a local file
  (no external requests) before any license activation or pixel
  enablement.
@Jmosier69 Jmosier69 merged commit bd89db5 into main Apr 10, 2026
2 checks passed
@Jmosier69 Jmosier69 deleted the emdash/feat-plugin-httpswordpress-5qg branch April 10, 2026 20:16
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