Clean up plugin options on uninstall - #58
Conversation
📝 WalkthroughWalkthroughThe plugin now removes its stored options and ChangesPlugin uninstall cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant WordPress
participant uninstall.php
participant PluginState
WordPress->>uninstall.php: invoke with WP_UNINSTALL_PLUGIN
uninstall.php->>PluginState: delete plugin options
uninstall.php->>PluginState: delete sendy_shops transient
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@tests/UninstallTest.php`:
- Around line 5-8: Update the deactivation test to initialize authenticated
modules before calling Plugin::instance()->deactivate(), injecting a fake API
connection so module cleanup executes. Add sendy_webhook_id,
sendy_webhook_last_checked, and sendy_shipping_methods_last_sync to the cleanup
options, and assert deactivation removes those values while preserving
configured options.
In `@uninstall.php`:
- Around line 3-6: Update the uninstall.php docblock to describe only the
cleanup it actually performs: deleting the listed options and the sendy_shops
transient. Remove the claim that all plugin data is removed or that reinstalling
always starts with fresh credentials and a new OAuth client.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 392126a2-0f53-4751-9a40-b75731996b18
📒 Files selected for processing (3)
readme.txttests/UninstallTest.phpuninstall.php
| /** | ||
| * Uninstalling must wipe all plugin options, so a shop that changes hands can | ||
| * reconnect with fresh credentials. Deactivating must leave them alone. | ||
| */ |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | grep -E '(^|/)(tests/(UninstallTest\.php|bootstrap\.php)|.*Plugin.*\.php|.*Module.*\.php)$' | head -200
printf '%s\n' '--- test file ---'
cat -n tests/UninstallTest.php
printf '%s\n' '--- bootstrap ---'
cat -n tests/bootstrap.phpRepository: sendynl/woocommerce-plugin
Length of output: 5209
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- Plugin outline ---'
ast-grep outline lib/Plugin.php --view compact
printf '%s\n' '--- Plugin implementation ---'
cat -n lib/Plugin.php
printf '%s\n' '--- deactivation and option cleanup references ---'
rg -n -C 5 'deactivat|sendy_webhook_id|sendy_webhook_last_checked|sendy_shipping_methods_last_sync|register_deactivation_hook|add_action' lib uninstall.php tests
printf '%s\n' '--- test doubles ---'
cat -n tests/doubles.phpRepository: sendynl/woocommerce-plugin
Length of output: 338
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- Plugin outline ---'
ast-grep outline lib/Plugin.php --view signatures
printf '%s\n' '--- Plugin implementation ---'
cat -n lib/Plugin.php
printf '%s\n' '--- deactivation and option cleanup references ---'
rg -n -C 5 'deactivat|sendy_webhook_id|sendy_webhook_last_checked|sendy_shipping_methods_last_sync|register_deactivation_hook|add_action' lib uninstall.php tests
printf '%s\n' '--- test doubles files ---'
git ls-files tests | grep -E '(^|/)doubles\.php$|(^|/)bootstrap.*\.php$'
printf '%s\n' '--- test doubles ---'
cat -n tests/doubles.phpRepository: sendynl/woocommerce-plugin
Length of output: 35189
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- plugin entrypoint and authentication ---'
cat -n sendy.php
rg -n -C 8 'function sendy_is_authenticated|sendy_is_authenticated|ApiClientFactory|TransportInterface|Fake_Transport|mock|MockObject|set_transport|buildConnectionUsingTokens' lib tests
printf '%s\n' '--- webhook module ---'
cat -n lib/Modules/Webhooks.php
printf '%s\n' '--- shipping synchronizer module ---'
cat -n lib/Modules/ShippingMethodsSynchronizer.php
printf '%s\n' '--- relevant project configuration ---'
git ls-files | grep -E '(^|/)(composer\.json|phpunit\.xml|phpunit\.xml\.dist|uninstall\.php)$' | xargs -r -n1 sh -c 'echo "--- $0"; cat -n "$0"'Repository: sendynl/woocommerce-plugin
Length of output: 44837
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- processing method enum ---'
cat -n lib/Enums/ProcessingMethod.php
printf '%s\n' '--- module constructors ---'
rg -n -U -C 3 'function __construct\(' lib/Modules
printf '%s\n' '--- tests that initialize Plugin or alter shared API state ---'
rg -n -C 4 'Plugin::instance|initialize_plugin|initialize_modules|ApiClientFactory|ReflectionProperty|sendy_access_token|sendy_processing_method' tests
printf '%s\n' '--- API factory ---'
cat -n lib/ApiClientFactory.php
printf '%s\n' '--- fake transport usage ---'
cat -n tests/PrintLabelsTest.php | sed -n '200,240p'Repository: sendynl/woocommerce-plugin
Length of output: 17271
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
plugin = Path("lib/Plugin.php").read_text()
webhooks = Path("lib/Modules/Webhooks.php").read_text()
shipping = Path("lib/Modules/ShippingMethodsSynchronizer.php").read_text()
test = Path("tests/UninstallTest.php").read_text()
helpers = Path("lib/helpers.php").read_text()
factory = Path("lib/ApiClientFactory.php").read_text()
def method_body(source, name):
match = re.search(
rf"function\s+{re.escape(name)}\s*\([^)]*\)[^{]*\{{(?P<body>.*?)\n\s*\}}",
source,
re.S,
)
return match.group("body") if match else ""
deactivate = method_body(plugin, "deactivate")
webhook_deactivate = method_body(webhooks, "deactivate")
shipping_deactivate = method_body(shipping, "deactivate")
print("Plugin::deactivate iterates stored modules:", "foreach ($this->modules as $module)" in deactivate)
print("Plugin::deactivate invokes module deactivate methods:",
"method_exists($module, 'deactivate')" in deactivate and "$module->deactivate()" in deactivate)
print("Authenticated module gate:", "if (sendy_is_authenticated())" in plugin)
print("Authentication requires a non-empty access token:",
"get_option('sendy_access_token') != ''" in helpers)
print("Webhooks cleanup options:",
sorted(re.findall(r"delete_option\('([^']+)'\)", webhook_deactivate + webhooks[webhooks.find("private function deleteWebhook"):webhooks.find("private function createWebhook")])) )
print("Shipping cleanup options:",
sorted(re.findall(r"delete_option\('([^']+)'\)", shipping_deactivate)))
print("Current deactivation test includes module-owned options:",
all(name in test for name in (
"sendy_webhook_id",
"sendy_webhook_last_checked",
"sendy_shipping_methods_last_sync",
)))
print("Webhook deactivation calls API for a configured webhook:",
"ApiClientFactory::buildConnectionUsingTokens()->webhook->delete($webhookId)" in webhooks)
print("API factory rejects empty token options:",
"throw new \\RuntimeException('Please authenticate first before using this method')" in factory)
PYRepository: sendynl/woocommerce-plugin
Length of output: 385
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
plugin = Path("lib/Plugin.php").read_text()
webhooks = Path("lib/Modules/Webhooks.php").read_text()
shipping = Path("lib/Modules/ShippingMethodsSynchronizer.php").read_text()
test = Path("tests/UninstallTest.php").read_text()
helpers = Path("lib/helpers.php").read_text()
factory = Path("lib/ApiClientFactory.php").read_text()
def method_body(source, name):
pattern = (
r"function\s+" + re.escape(name)
+ r"\s*\([^)]*\)[^{]*\{(?P<body>.*?)\n\s*\}"
)
match = re.search(pattern, source, re.S)
return match.group("body") if match else ""
deactivate = method_body(plugin, "deactivate")
webhook_deactivate = method_body(webhooks, "deactivate")
shipping_deactivate = method_body(shipping, "deactivate")
delete_webhook = webhooks[
webhooks.find("private function deleteWebhook"):
webhooks.find("private function createWebhook")
]
print("Plugin::deactivate iterates stored modules:",
"foreach ($this->modules as $module)" in deactivate)
print("Plugin::deactivate invokes module deactivate methods:",
"method_exists($module, 'deactivate')" in deactivate
and "$module->deactivate()" in deactivate)
print("Authenticated module gate:",
"if (sendy_is_authenticated())" in plugin)
print("Authentication requires a non-empty access token:",
"get_option('sendy_access_token') != ''" in helpers)
print("Webhooks cleanup options:",
sorted(set(re.findall(r"delete_option\('([^']+)'\)",
webhook_deactivate + delete_webhook))))
print("Shipping cleanup options:",
sorted(set(re.findall(r"delete_option\('([^']+)'\)",
shipping_deactivate))))
print("Current deactivation test includes module-owned options:",
all(name in test for name in (
"sendy_webhook_id",
"sendy_webhook_last_checked",
"sendy_shipping_methods_last_sync",
)))
print("Webhook deactivation calls API for a configured webhook:",
"ApiClientFactory::buildConnectionUsingTokens()->webhook->delete($webhookId)"
in webhooks)
print("API factory rejects empty token options:",
"throw new \\RuntimeException('Please authenticate first before using this method')"
in factory)
PYRepository: sendynl/woocommerce-plugin
Length of output: 671
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
plugin = Path("lib/Plugin.php").read_text()
webhooks = Path("lib/Modules/Webhooks.php").read_text()
shipping = Path("lib/Modules/ShippingMethodsSynchronizer.php").read_text()
def balanced_method_body(source, signature):
start = source.index(signature)
open_brace = source.index("{", start)
depth = 0
for index in range(open_brace, len(source)):
if source[index] == "{":
depth += 1
elif source[index] == "}":
depth -= 1
if depth == 0:
return source[open_brace + 1:index]
raise AssertionError("unbalanced method")
plugin_deactivate = balanced_method_body(
plugin, "public function deactivate(): void"
)
webhook_deactivate = balanced_method_body(
webhooks, "public function deactivate(): void"
)
shipping_deactivate = balanced_method_body(
shipping, "public function deactivate(): void"
)
delete_start = webhooks.index("private function deleteWebhook")
delete_end = webhooks.index("private function createWebhook")
delete_webhook = webhooks[delete_start:delete_end]
print("Plugin::deactivate iterates stored modules:",
"foreach ($this->modules as $module)" in plugin_deactivate)
print("Plugin::deactivate invokes module deactivate methods:",
"method_exists($module, 'deactivate')" in plugin_deactivate
and "$module->deactivate()" in plugin_deactivate)
print("Webhooks cleanup options:",
sorted(set(re.findall(r"delete_option\('([^']+)'\)",
webhook_deactivate + delete_webhook))))
print("Shipping cleanup options:",
sorted(set(re.findall(r"delete_option\('([^']+)'\)",
shipping_deactivate))))
print("Webhook API delete is conditional on a configured webhook ID:",
"if ($webhookId)" in delete_webhook
and "webhook->delete($webhookId)" in delete_webhook)
PYRepository: sendynl/woocommerce-plugin
Length of output: 479
Make the deactivation test exercise module cleanup.
Plugin::instance()->deactivate() only deactivates initialized modules. Load the authenticated modules and inject a fake API connection. Keep sendy_webhook_id, sendy_webhook_last_checked, and sendy_shipping_methods_last_sync in a cleanup list, and assert that deactivation deletes them while configured options remain unchanged.
🤖 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 `@tests/UninstallTest.php` around lines 5 - 8, Update the deactivation test to
initialize authenticated modules before calling
Plugin::instance()->deactivate(), injecting a fake API connection so module
cleanup executes. Add sendy_webhook_id, sendy_webhook_last_checked, and
sendy_shipping_methods_last_sync to the cleanup options, and assert deactivation
removes those values while preserving configured options.
Source: MCP tools
| /** | ||
| * Removes all plugin data when the plugin is uninstalled, so a reinstall | ||
| * starts with fresh credentials and the portal creates a new OauthClient. | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Narrow the uninstall scope in the docblock.
This file deletes the listed options and the sendy_shops transient. It does not delete all plugin-owned state, such as _sendy_* order metadata or the scheduled sendy_cron event. If those records remain intentionally, document the exact cleanup scope instead. (github.com/ghraw)
🤖 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 `@uninstall.php` around lines 3 - 6, Update the uninstall.php docblock to
describe only the cleanup it actually performs: deleting the listed options and
the sendy_shops transient. Remove the claim that all plugin data is removed or
that reinstalling always starts with fresh credentials and a new OAuth client.
Source: MCP tools
Summary by CodeRabbit
Bug Fixes
Documentation