Skip to content

Clean up plugin options on uninstall - #58

Open
adriaanzon wants to merge 1 commit into
mainfrom
cleanup-options-on-uninstall
Open

Clean up plugin options on uninstall#58
adriaanzon wants to merge 1 commit into
mainfrom
cleanup-options-on-uninstall

Conversation

@adriaanzon

@adriaanzon adriaanzon commented Aug 3, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • Bug Fixes

    • Uninstalling the plugin now removes saved plugin settings and cached shop data.
    • Deactivating the plugin continues to preserve existing settings, allowing them to be retained for future use.
  • Documentation

    • Added an unreleased changelog entry documenting the uninstall cleanup behavior.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The plugin now removes its stored options and sendy_shops transient during uninstall. New tests verify cleanup and confirm that deactivation preserves plugin options. The changelog documents the behavior.

Changes

Plugin uninstall cleanup

Layer / File(s) Summary
Uninstall state cleanup
uninstall.php
The uninstall handler checks WP_UNINSTALL_PLUGIN, deletes the listed plugin options, and removes the sendy_shops transient.
Uninstall and deactivation validation
tests/UninstallTest.php, readme.txt
Tests cover option deletion, transient deletion, uninstall invocation, and option preservation during deactivation. The changelog records the cleanup behavior.

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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: removing plugin options during uninstall.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cleanup-options-on-uninstall

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

📥 Commits

Reviewing files that changed from the base of the PR and between e0675c8 and 6ed999f.

📒 Files selected for processing (3)
  • readme.txt
  • tests/UninstallTest.php
  • uninstall.php

Comment thread tests/UninstallTest.php
Comment on lines +5 to +8
/**
* Uninstalling must wipe all plugin options, so a shop that changes hands can
* reconnect with fresh credentials. Deactivating must leave them alone.
*/

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.

🗄️ 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.php

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

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

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

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

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

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

Comment thread uninstall.php
Comment on lines +3 to +6
/**
* Removes all plugin data when the plugin is uninstalled, so a reinstall
* starts with fresh credentials and the portal creates a new OauthClient.
*/

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.

📐 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

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.

2 participants