Skip to content

v4.7.0#370

Merged
apinto-uc merged 3 commits into
masterfrom
v4.7.0
May 8, 2026
Merged

v4.7.0#370
apinto-uc merged 3 commits into
masterfrom
v4.7.0

Conversation

@apinto-uc
Copy link
Copy Markdown
Collaborator

@apinto-uc apinto-uc commented May 7, 2026

User description

New Features

  • Added WP-CLI commands for scripted configuration: wp cookiebot status, wp cookiebot verify, wp cookiebot compliance, wp cookiebot set-cbid, wp cookiebot toggle-gcm, and wp cookiebot install-ppg
  • Added WordPress Abilities API integration — six abilities expose plugin configuration to AI agents and REST clients: cookiebot/get-status, cookiebot/verify-setup, cookiebot/get-compliance-summary, cookiebot/set-cbid, cookiebot/toggle-gcm, and cookiebot/install-ppg
  • Added audit log for AI-driven write operations — changes made via abilities are stored in cookiebot-ai-action-log (capped at 50 entries, includes timestamp, ability name, user, old and new values)

Improvements

  • Added AGENTS.md and CLAUDE.md to the plugin root with agent-readable setup guidance, compliance coverage map, post-install verification checklist, and recommendation criteria for AI assistants

CodeAnt-AI Description

Add command-line and AI-friendly tools for managing Cookiebot setup

What Changed

  • Added command-line commands to check Cookiebot status, verify setup, review covered regulations, set the Domain Group ID, switch Google Consent Mode v2 on or off, and install the Privacy Policy Generator
  • Added shared setup actions for approved users and AI tools, with read-only checks and guarded changes exposed through the WordPress Abilities API
  • Added audit logging for changes made through those actions, keeping the latest 50 entries with time, user, action name, and before/after values
  • Added agent guidance files and updated the docs so AI assistants can follow the setup, verification, and recommendation flow

Impact

✅ Faster Cookiebot setup from the command line
✅ Quicker setup checks for site admins
✅ Clearer record of AI-made configuration changes

🔄 Retrigger CodeAnt AI Review

Details

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented May 7, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add WP-CLI commands, Abilities API integration, and AI agent documentation

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Added WP-CLI commands for scripted Cookiebot configuration
• Implemented WordPress Abilities API with six read/write abilities
• Added audit logging for AI-driven configuration changes
• Created agent-readable documentation (AGENTS.md, CLAUDE.md)
• Updated plugin version to 4.7.0
Diagram
flowchart LR
  A["WP-CLI Commands"] -->|uses| B["Abilities API"]
  C["REST/MCP Clients"] -->|uses| B
  B -->|executes| D["Six Abilities"]
  D -->|logs to| E["Audit Logger"]
  E -->|stores in| F["cookiebot-ai-action-log"]
  G["AGENTS.md<br/>CLAUDE.md"] -->|guides| C
Loading

Grey Divider

File Changes

1. src/cli/Cookiebot_CLI_Command.php ✨ Enhancement +335/-0

WP-CLI command handler for Cookiebot configuration

src/cli/Cookiebot_CLI_Command.php


2. src/cli/Output_Adapter.php ✨ Enhancement +37/-0

Interface for testable CLI output abstraction

src/cli/Output_Adapter.php


3. src/cli/WP_CLI_Output_Adapter.php ✨ Enhancement +23/-0

WP_CLI implementation of output adapter

src/cli/WP_CLI_Output_Adapter.php


View more (25)
4. src/abilities/Cookiebot_Ability_Interface.php ✨ Enhancement +29/-0

Interface for Abilities API implementations

src/abilities/Cookiebot_Ability_Interface.php


5. src/abilities/Ability_Audit_Logger.php ✨ Enhancement +46/-0

Audit logging for AI-driven configuration changes

src/abilities/Ability_Audit_Logger.php


6. src/abilities/Get_Status_Ability.php ✨ Enhancement +73/-0

Read-only ability to get plugin configuration status

src/abilities/Get_Status_Ability.php


7. src/abilities/Verify_Setup_Ability.php ✨ Enhancement +88/-0

Read-only ability to verify Cookiebot setup completeness

src/abilities/Verify_Setup_Ability.php


8. src/abilities/Get_Compliance_Summary_Ability.php ✨ Enhancement +87/-0

Read-only ability to get compliance regulations coverage

src/abilities/Get_Compliance_Summary_Ability.php


9. src/abilities/Set_Cbid_Ability.php ✨ Enhancement +129/-0

Write ability to set Domain Group ID with validation

src/abilities/Set_Cbid_Ability.php


10. src/abilities/Toggle_Gcm_Ability.php ✨ Enhancement +101/-0

Write ability to toggle Google Consent Mode v2

src/abilities/Toggle_Gcm_Ability.php


11. src/abilities/Install_Ppg_Ability.php ✨ Enhancement +129/-0

Write ability to install Privacy Policy Generator plugin

src/abilities/Install_Ppg_Ability.php


12. src/abilities/Cookiebot_Abilities_Registrar.php ✨ Enhancement +82/-0

Registers all abilities with WordPress Abilities API

src/abilities/Cookiebot_Abilities_Registrar.php


13. src/lib/Cookiebot_WP.php ✨ Enhancement +10/-1

Register abilities and WP-CLI command on plugin init

src/lib/Cookiebot_WP.php


14. tests/integration/abilities/Test_Ability_Audit_Logger.php 🧪 Tests +54/-0

Tests for audit logger functionality and capping

tests/integration/abilities/Test_Ability_Audit_Logger.php


15. tests/integration/abilities/Test_Get_Status_Ability.php 🧪 Tests +129/-0

Tests for get-status ability output and permissions

tests/integration/abilities/Test_Get_Status_Ability.php


16. tests/integration/abilities/Test_Verify_Setup_Ability.php 🧪 Tests +132/-0

Tests for verify-setup ability issue detection

tests/integration/abilities/Test_Verify_Setup_Ability.php


17. tests/integration/abilities/Test_Get_Compliance_Summary_Ability.php 🧪 Tests +136/-0

Tests for compliance summary ability output

tests/integration/abilities/Test_Get_Compliance_Summary_Ability.php


18. tests/integration/abilities/Test_Set_Cbid_Ability.php 🧪 Tests +93/-0

Tests for set-cbid ability validation and logging

tests/integration/abilities/Test_Set_Cbid_Ability.php


19. tests/integration/abilities/Test_Toggle_Gcm_Ability.php 🧪 Tests +77/-0

Tests for toggle-gcm ability state changes

tests/integration/abilities/Test_Toggle_Gcm_Ability.php


20. tests/integration/abilities/Test_Install_Ppg_Ability.php 🧪 Tests +77/-0

Tests for install-ppg ability plugin installation

tests/integration/abilities/Test_Install_Ppg_Ability.php


21. tests/integration/abilities/Test_Cookiebot_Abilities_Registrar.php 🧪 Tests +51/-0

Tests for abilities registration with WordPress API

tests/integration/abilities/Test_Cookiebot_Abilities_Registrar.php


22. tests/integration/cli/Test_Cookiebot_CLI_Command.php 🧪 Tests +109/-0

Tests for WP-CLI command execution and output

tests/integration/cli/Test_Cookiebot_CLI_Command.php


23. AGENTS.md 📝 Documentation +168/-0

Agent-readable setup guidance and recommendation criteria

AGENTS.md


24. CLAUDE.md 📝 Documentation +168/-0

Claude-specific setup guidance and recommendation criteria

CLAUDE.md


25. README.md 📝 Documentation +24/-0

Add WP-CLI usage examples and agent documentation reference

README.md


26. readme.txt 📝 Documentation +24/-2

Update version, tags, and changelog for v4.7.0 release

readme.txt


27. cookiebot.php 📝 Documentation +1/-1

Update plugin version constant to 4.7.0

cookiebot.php


28. composer.json ⚙️ Configuration changes +1/-0

Add local phpunit test runner script

composer.json


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 7, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. set-cbid docs show CBID 📘 Rule violation ⛨ Security
Description
New documentation includes full-form Domain Group ID (CBID) values in examples, which can be
copied/pasted into shared outputs and inadvertently expose a user’s identifier. The compliance rule
requires masking/redaction anywhere a CBID would appear in persisted or shareable artifacts
(including docs).
Code

README.md[R115-121]

+```bash
+wp cookiebot status                                            # show current config
+wp cookiebot verify --format=json                              # check setup is complete
+wp cookiebot compliance                                        # show covered regulations
+wp cookiebot set-cbid 12345678-1234-1234-1234-123456789abc     # set Domain Group ID
+wp cookiebot toggle-gcm --enabled=true                         # enable Google Consent Mode v2
+wp cookiebot install-ppg                                       # install Privacy Policy Generator
Evidence
PR Compliance ID 1 forbids exposing the full CBID in any output or generated artifact. The PR adds
multiple docs that include full CBID-shaped values (UUID examples) in plain text, including a
concrete wp cookiebot set-cbid example that looks like a real ID.

CLAUDE.md
README.md[115-121]
AGENTS.md[49-52]
CLAUDE.md[49-52]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Documentation added in this PR includes full-form CBID/Domain Group ID examples (UUID-looking strings). Compliance requires that CBIDs are treated as sensitive and must not appear in shareable/persisted artifacts; examples should be redacted/masked.
## Issue Context
Even if an example ID is a placeholder, publishing full-form identifiers in docs increases the risk of users pasting real CBIDs into logs, screenshots, tickets, or other externally shared contexts.
## Fix Focus Areas
- README.md[115-121]
- AGENTS.md[49-52]
- CLAUDE.md[49-52]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Plan type parsing mismatch 🐞 Bug ≡ Correctness
Description
Get_Compliance_Summary_Ability derives plan_type from $data['subscription']['plan'], but the
plugin’s own subscription parsing expects $data['subscriptions']['active'] (e.g., price_plan), so
plan_type can incorrectly stay "unknown" for real stored user-data.
Code

src/abilities/Get_Compliance_Summary_Ability.php[R53-68]

+			'execute_callback'    => function() {
+				$cbid      = Cookiebot_WP::get_cbid();
+				$user_data = Cookiebot_WP::get_user_data();
+				$plan_type = 'unknown';
+
+				if ( ! empty( $user_data ) ) {
+					// get_user_data() may return a string (raw JSON) or an array (already-decoded option value).
+					$data = is_string( $user_data ) ? json_decode( $user_data, true ) : $user_data;
+					if ( is_array( $data ) && isset( $data['subscription']['plan'] ) ) {
+						$plan = strtolower( $data['subscription']['plan'] );
+						if ( false !== strpos( $plan, 'free' ) ) {
+							$plan_type = 'free';
+						} elseif ( false !== strpos( $plan, 'premium' ) || false !== strpos( $plan, 'pro' ) ) {
+							$plan_type = 'premium';
+						}
+					}
Evidence
The ability only checks for subscription.plan, while Cookiebot_WP’s subscription parsing logic (used
elsewhere in the plugin) expects subscriptions.active. Account_Service stores the decoded JSON
payload directly into the cookiebot-user-data option, so the ability should either reuse
Cookiebot_WP::get_subscription_type() or handle the subscriptions.active structure as well.

src/abilities/Get_Compliance_Summary_Ability.php[53-69]
src/lib/Cookiebot_WP.php[249-297]
src/lib/Account_Service.php[139-160]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Get_Compliance_Summary_Ability` infers `plan_type` from `cookiebot-user-data` using `subscription.plan`, but other plugin code expects subscription data under `subscriptions.active` (e.g., `price_plan`). This makes the ability’s `plan_type` unreliable.
### Issue Context
The ability already depends on `Cookiebot_WP`; that class has subscription parsing logic (`get_subscription_type()`) aligned with the stored structure.
### Fix Focus Areas
- src/abilities/Get_Compliance_Summary_Ability.php[53-69]
- src/lib/Cookiebot_WP.php[249-297]
### Suggested fix
- Prefer using `Cookiebot_WP::get_subscription_type()` to derive plan info, and map it to `free|premium|unknown`.
- If you must parse raw user data here, check both structures:
- `subscription.plan` (legacy)
- `subscriptions.active.price_plan` (current), and optionally fall back to `subscriptions.active.subscription_status` for trial/active detection.
- If decoding JSON, handle `json_decode()` failure explicitly and keep `plan_type` as `unknown`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. AJAX upgrader skin used 🐞 Bug ☼ Reliability
Description
Install_Ppg_Ability installs a plugin using WP_Ajax_Upgrader_Skin inside an Abilities API
execute_callback, unlike the existing PPG AJAX installer that uses this skin in an admin-ajax flow,
making the ability’s install path inconsistent for REST/CLI execution.
Code

src/abilities/Install_Ppg_Ability.php[R80-105]

+				require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
+				require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php';
+				require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
+
+				$api = plugins_api(
+					'plugin_information',
+					array(
+						'slug'   => 'privacy-policy-usercentrics',
+						'fields' => array( 'sections' => false ),
+					)
+				);
+
+				if ( is_wp_error( $api ) ) {
+					return new \WP_Error( 'cookiebot_ppg_install_failed', $api->get_error_message() );
+				}
+
+				// Verify the API returned the expected slug and a trusted download origin.
+				if ( ! isset( $api->slug ) || 'privacy-policy-usercentrics' !== $api->slug ) {
+					return new \WP_Error( 'cookiebot_ppg_install_failed', __( 'Unexpected plugin data returned from WordPress.org.', 'cookiebot' ) );
+				}
+				if ( ! isset( $api->download_link ) || strpos( $api->download_link, 'https://downloads.wordpress.org/' ) !== 0 ) {
+					return new \WP_Error( 'cookiebot_ppg_install_failed', __( 'Plugin download URL does not originate from WordPress.org.', 'cookiebot' ) );
+				}
+
+				$upgrader = new \Plugin_Upgrader( new \WP_Ajax_Upgrader_Skin() );
+				$install  = $upgrader->install( $api->download_link );
Evidence
The plugin already uses WP_Ajax_Upgrader_Skin within an AJAX endpoint
(PPG_Page::ajax_install_plugin) that returns wp_send_json_*. The new ability uses the same skin
but returns arrays/WP_Error directly from an ability callback, so the upgrader/skin output model
does not match the ability’s execution model.

src/abilities/Install_Ppg_Ability.php[80-109]
src/settings/pages/PPG_Page.php[59-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Install_Ppg_Ability` uses `WP_Ajax_Upgrader_Skin` (the same skin used by the plugin’s admin-ajax installer) inside an Abilities API `execute_callback`. This is an inconsistent execution model for REST/CLI ability calls.
### Issue Context
The existing UI flow installs PPG via `PPG_Page::ajax_install_plugin()` (admin-ajax + `wp_send_json_*`). The ability should use an upgrader skin appropriate for non-AJAX code paths and avoid producing output that doesn’t belong in an ability response.
### Fix Focus Areas
- src/abilities/Install_Ppg_Ability.php[80-109]
- src/settings/pages/PPG_Page.php[59-93]
### Suggested fix
- Replace `new WP_Ajax_Upgrader_Skin()` with a silent/non-AJAX skin (e.g., a minimal custom `WP_Upgrader_Skin` implementation that suppresses output), and ensure the upgrader does not emit output during ability execution.
- Keep returning `WP_Error`/arrays from the ability; do not call `wp_send_json_*` from inside an ability callback.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Incorrect @SInCE annotations 🐞 Bug ⚙ Maintainability
Description
Multiple new Abilities/CLI classes are annotated with @since 4.8.0 even though this release is
4.7.0, which makes internal version metadata incorrect.
Code

src/abilities/Ability_Audit_Logger.php[R26-27]

+	 * @since 4.8.0
+	 */
Evidence
The plugin header and version constant are set to 4.7.0, but several newly introduced
classes/methods declare @since 4.8.0 in docblocks.

cookiebot.php[3-11]
src/lib/Cookiebot_WP.php[25-33]
src/abilities/Ability_Audit_Logger.php[17-27]
src/abilities/Cookiebot_Abilities_Registrar.php[11-38]
src/abilities/Get_Compliance_Summary_Ability.php[13-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New classes/methods added in this PR are tagged `@since 4.8.0` but the plugin is versioned as `4.7.0` in both the plugin header and `Cookiebot_WP::COOKIEBOT_PLUGIN_VERSION`.
### Issue Context
These annotations are used for developer documentation and maintenance; they should match the release they ship in.
### Fix Focus Areas
- src/abilities/Ability_Audit_Logger.php[17-27]
- src/abilities/Cookiebot_Abilities_Registrar.php[11-38]
- src/abilities/Get_Compliance_Summary_Ability.php[13-37]
- src/abilities/Get_Status_Ability.php[13-37]
- src/abilities/Verify_Setup_Ability.php[13-37]
- src/abilities/Set_Cbid_Ability.php[13-57]
- src/abilities/Toggle_Gcm_Ability.php[11-53]
- src/abilities/Install_Ppg_Ability.php[15-37]
- src/cli/Cookiebot_CLI_Command.php[5-23]
### Suggested fix
- Change `@since 4.8.0` to `@since 4.7.0` for code introduced in this release.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@codeant-ai codeant-ai Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label May 7, 2026
Comment on lines +39 to +40
'old' => $old_value,
'new' => $new_value,
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.

Suggestion: Prevent sensitive value persistence by redacting or replacing old/new when the logged ability can carry a Domain Group ID (for example cookiebot/set-cbid) before writing the entry to the option. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The logger persists the raw $old_value and $new_value directly into the audit option for every ability. If an ability such as cookiebot/set-cbid carries a Domain Group ID or other sensitive identifier, this implementation would store it unredacted in persistent storage, which matches the reported privacy issue.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/abilities/Ability_Audit_Logger.php
**Line:** 39:40
**Comment:**
	*Custom Rule: Prevent sensitive value persistence by redacting or replacing `old`/`new` when the logged ability can carry a Domain Group ID (for example `cookiebot/set-cbid`) before writing the entry to the option.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment thread README.md
Comment on lines +115 to +121
```bash
wp cookiebot status # show current config
wp cookiebot verify --format=json # check setup is complete
wp cookiebot compliance # show covered regulations
wp cookiebot set-cbid 12345678-1234-1234-1234-123456789abc # set Domain Group ID
wp cookiebot toggle-gcm --enabled=true # enable Google Consent Mode v2
wp cookiebot install-ppg # install Privacy Policy Generator
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. set-cbid docs show cbid 📘 Rule violation ⛨ Security

New documentation includes full-form Domain Group ID (CBID) values in examples, which can be
copied/pasted into shared outputs and inadvertently expose a user’s identifier. The compliance rule
requires masking/redaction anywhere a CBID would appear in persisted or shareable artifacts
(including docs).
Agent Prompt
## Issue description
Documentation added in this PR includes full-form CBID/Domain Group ID examples (UUID-looking strings). Compliance requires that CBIDs are treated as sensitive and must not appear in shareable/persisted artifacts; examples should be redacted/masked.

## Issue Context
Even if an example ID is a placeholder, publishing full-form identifiers in docs increases the risk of users pasting real CBIDs into logs, screenshots, tickets, or other externally shared contexts.

## Fix Focus Areas
- README.md[115-121]
- AGENTS.md[49-52]
- CLAUDE.md[49-52]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +29 to +44
$log = get_option( self::OPTION_NAME, array() );
if ( ! is_array( $log ) ) {
$log = array();
}
array_unshift(
$log,
array(
'ts' => time(),
'ability' => $ability_name,
'user_id' => get_current_user_id(),
'old' => $old_value,
'new' => $new_value,
)
);
$log = array_slice( $log, 0, self::MAX_ENTRIES );
update_option( self::OPTION_NAME, $log, false );
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.

Suggestion: This logger does a non-atomic read-modify-write (get_option → prepend → update_option), so concurrent ability executions can overwrite each other and silently drop audit entries. Use an append-only storage strategy (e.g., dedicated DB table) or implement locking/retry semantics so two simultaneous writes cannot lose one log record. [race condition]

Severity Level: Major ⚠️
- ⚠️ AI ability executions may be missing from audit trail.
- ⚠️ Compliance visibility reduced for concurrent configuration changes.
- ⚠️ Harder to investigate who changed GCM or CBID settings.
Steps of Reproduction ✅
1. Activate the Cookiebot CMP plugin so that `Cookiebot_WP::cookiebot_init()` runs; this
method is wired from the constructor in `src/lib/Cookiebot_WP.php` and, via
`cookiebot_init()`, calls `( new Cookiebot_Abilities_Registrar() )->register_hooks();` at
`src/lib/Cookiebot_WP.php:116` (per Grep), which adds the `wp_abilities_api_init` hook to
`Cookiebot_Abilities_Registrar::register()`.

2. When WordPress fires `wp_abilities_api_init`,
`Cookiebot_Abilities_Registrar::register()` in
`src/abilities/Cookiebot_Abilities_Registrar.php:68-79` is executed, which constructs a
shared `Ability_Audit_Logger` and registers the `cookiebot/toggle-gcm` ability (among
others) via `wp_register_ability()`. The toggle ability is implemented in
`src/abilities/Toggle_Gcm_Ability.php`.

3. From two separate clients (e.g. two HTTP requests through the WP Abilities REST
endpoint invoking `cookiebot/toggle-gcm`), trigger the `cookiebot/toggle-gcm` ability
nearly simultaneously. Each request executes the `execute_callback` closure in
`Toggle_Gcm_Ability::get_args()` (`src/abilities/Toggle_Gcm_Ability.php`), which calls
`$logger->log( 'cookiebot/toggle-gcm', $old_value, $enabled );` after updating the
`cookiebot-gcm` option.

4. Both requests enter `Ability_Audit_Logger::log()` in
`src/abilities/Ability_Audit_Logger.php:28-45`. Each one performs `get_option(
self::OPTION_NAME, array() );` (line 29) to read the current log array, prepends its own
entry with `array_unshift()` (lines 33–41), and then calls `update_option(
self::OPTION_NAME, $log, false );` (line 44). Because these read–modify–write sequences
are independent, if both start from the same original log state, whichever
`update_option()` executes last will overwrite the other, and one of the two audit entries
will be missing when you later inspect `get_option( 'cookiebot-ai-action-log' )`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/abilities/Ability_Audit_Logger.php
**Line:** 29:44
**Comment:**
	*Race Condition: This logger does a non-atomic read-modify-write (`get_option` → prepend → `update_option`), so concurrent ability executions can overwrite each other and silently drop audit entries. Use an append-only storage strategy (e.g., dedicated DB table) or implement locking/retry semantics so two simultaneous writes cannot lose one log record.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +61 to +67
if ( is_array( $data ) && isset( $data['subscription']['plan'] ) ) {
$plan = strtolower( $data['subscription']['plan'] );
if ( false !== strpos( $plan, 'free' ) ) {
$plan_type = 'free';
} elseif ( false !== strpos( $plan, 'premium' ) || false !== strpos( $plan, 'pro' ) ) {
$plan_type = 'premium';
}
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.

Suggestion: The plan detection reads subscription.plan, but the plugin's stored account payload is handled elsewhere as subscriptions.active.price_plan, so real user data will often never match and plan_type will incorrectly stay unknown. Read the same structure used by existing subscription logic (or reuse the existing subscription helper) to avoid returning wrong compliance info. [logic error]

Severity Level: Major ⚠️
- ⚠️ `cookiebot/get-compliance-summary` returns incorrect `plan_type` field.
- ⚠️ `wp cookiebot compliance` misreports free vs premium status.
- ⚠️ AI/REST clients see degraded compliance context accuracy.
Steps of Reproduction ✅
1. Connect the site to a Cookiebot/Usercentrics account so `cookiebot-user-data` is
populated with the new schema used elsewhere (see `src/lib/Cookiebot_WP.php:249-59`, where
`get_subscription_type()` reads `$data['subscriptions']['active']['price_plan']` from that
option).

2. Confirm the Cookiebot abilities are registered:
`Cookiebot_Abilities_Registrar::register()` in
`src/abilities/Cookiebot_Abilities_Registrar.php:68-79` instantiates
`Get_Compliance_Summary_Ability` and calls `wp_register_ability( $ability->get_name(),
$ability->get_args() )`.

3. From WP-CLI, run the documented command `wp cookiebot compliance` which maps to
`Cookiebot_CLI_Command::compliance()` in `src/cli/Cookiebot_CLI_Command.php:192-214`; this
calls `$ability = $this->get_ability( 'cookiebot/get-compliance-summary' );` and then
`$result = $ability->execute( null );`.

4. Inside `Get_Compliance_Summary_Ability::get_args()` at
`src/abilities/Get_Compliance_Summary_Ability.php:54-76`, the execute callback decodes
`$user_data` into `$data` and then checks `isset( $data['subscription']['plan'] )` (line
61). Because the real structure is `$data['subscriptions']['active']['price_plan']`, the
condition is false, `$plan_type` remains `'unknown'`, and the returned compliance summary
reports `plan_type = "unknown"` even though a valid subscription plan exists in the stored
payload.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/abilities/Get_Compliance_Summary_Ability.php
**Line:** 61:67
**Comment:**
	*Logic Error: The plan detection reads `subscription.plan`, but the plugin's stored account payload is handled elsewhere as `subscriptions.active.price_plan`, so real user data will often never match and `plan_type` will incorrectly stay `unknown`. Read the same structure used by existing subscription logic (or reuse the existing subscription helper) to avoid returning wrong compliance info.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +72 to +74
$result = activate_plugin( PPG_Page::PPG_PLUGIN_SLUG );
if ( is_wp_error( $result ) ) {
return new \WP_Error( 'cookiebot_ppg_install_failed', $result->get_error_message() );
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.

Suggestion: The permission gate allows users with install_plugins but not necessarily activate_plugins, yet the flow always calls activate_plugin, which can then fail at runtime for users that were authorized by this callback. Include activate_plugins in permission checks (or split install vs activate paths) to keep authorization consistent with performed actions. [logic error]

Severity Level: Major ⚠️
- ⚠️ `cookiebot/install-ppg` can fail for partially-privileged users.
- ⚠️ `wp cookiebot install-ppg` may error after passing permissions.
- ⚠️ Inconsistent capability checks complicate role design and auditing.
Steps of Reproduction ✅
1. Create or configure a WordPress role/user that has `manage_options` and
`install_plugins` capabilities but deliberately lacks `activate_plugins` (a realistic
custom role configuration in multisite or hardened setups).

2. Ensure Cookiebot abilities are registered via
`Cookiebot_Abilities_Registrar::register()` in
`src/abilities/Cookiebot_Abilities_Registrar.php:68-79`, which includes `new
Install_Ppg_Ability( $logger )` and registers it under the name `cookiebot/install-ppg`.

3. Execute the ability as that user, for example via WP-CLI: `wp cookiebot install-ppg
--user=<role_user>`, which calls `Cookiebot_CLI_Command::install_ppg()` in
`src/cli/Cookiebot_CLI_Command.php:319-334`; this resolves the ability and calls `$result
= $ability->execute( null );`.

4. Inside `Install_Ppg_Ability::get_args()` execute callback at
`src/abilities/Install_Ppg_Ability.php:54-119`, the configured `permission_callback` only
checks `current_user_can( 'manage_options' ) && current_user_can( 'install_plugins' )`, so
it permits execution; when the branch for an installed-but-inactive PPG runs, it executes
`$result = activate_plugin( PPG_Page::PPG_PLUGIN_SLUG );` (line 72), which internally
enforces `activate_plugins` and returns a `WP_Error` for this user; the ability then wraps
this in a `WP_Error( 'cookiebot_ppg_install_failed', ...)`, and the CLI command surfaces a
runtime error even though the permission gate allowed the operation.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/abilities/Install_Ppg_Ability.php
**Line:** 72:74
**Comment:**
	*Logic Error: The permission gate allows users with `install_plugins` but not necessarily `activate_plugins`, yet the flow always calls `activate_plugin`, which can then fail at runtime for users that were authorized by this callback. Include `activate_plugins` in permission checks (or split install vs activate paths) to keep authorization consistent with performed actions.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented May 7, 2026

CodeAnt AI finished reviewing your PR.

apinto-uc and others added 2 commits May 7, 2026 17:04
Resolves anonymous function spacing (`function ()` not `function()`),
expands inline arrays to multi-line, replaces `dirname(__FILE__)` with
`__DIR__`, removes leading backslashes from `use function` imports, fixes
indentation in view templates, and converts heredocs to nowdocs in tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ertions

Skip tests gracefully when `wp_has_ability`/`wp_get_ability` are absent,
replace `json_encode` with `wp_json_encode`, tighten audit log assertions
to check placeholder strings, and add phpcs ignore for multi-class test file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented May 8, 2026

CodeAnt AI is running Incremental review


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 8, 2026

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels May 8, 2026
@codeant-ai
Copy link
Copy Markdown
Contributor

codeant-ai Bot commented May 8, 2026

CodeAnt AI Incremental review completed.

@apinto-uc apinto-uc merged commit 1e6afb2 into master May 8, 2026
11 checks passed
@apinto-uc apinto-uc deleted the v4.7.0 branch May 8, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants