From c868eae35fa538cf4c2d30fb51488fe82a076f0f Mon Sep 17 00:00:00 2001 From: Malin Date: Mon, 3 Aug 2026 06:25:28 +0200 Subject: [PATCH] fix: wordpress-plugin-rebrand's updater-wiring example could reintroduce the dead-updater bug on singleton-class plugins --- skills/wordpress-plugin-rebrand/SKILL.md | 50 ++++++++++++++++++------ 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/skills/wordpress-plugin-rebrand/SKILL.md b/skills/wordpress-plugin-rebrand/SKILL.md index f24a604..4ce54bd 100644 --- a/skills/wordpress-plugin-rebrand/SKILL.md +++ b/skills/wordpress-plugin-rebrand/SKILL.md @@ -80,20 +80,44 @@ renames were consistent. ## Step 5 — Bundle the updater / license client (if applicable) Copy the shared updater class unchanged into the plugin's `includes/` -directory. Wire it in the main file (near the top, after constants): +directory. **Where you wire it in depends on the plugin's own +architecture — get this wrong and license/update-checking silently never +runs, with no error anywhere** (see the full incident writeup in +`wordpress-plugin-conventions`'s "Never add_action at a lower priority +from inside a callback on the same hook" section): -```php -require_once . 'includes/class-.php'; -add_action( 'plugins_loaded', function () { - new ( [ - 'api_url' => '', - 'plugin_file' => __FILE__, - 'plugin_slug' => '', - 'version' => , - 'license_option' => '__license_key', - ] ); -}, 5 ); -``` +- **If the plugin is flat/procedural** (no singleton class hooked to + `plugins_loaded` itself — the 5 original iWP forks are this shape), a + top-level call in the main file is safe: + ```php + require_once . 'includes/class-.php'; + add_action( 'plugins_loaded', function () { + new ( [ /* ... */ ] ); + }, 5 ); + ``` + This works because the file itself loads (and this `add_action` call + runs) BEFORE `plugins_loaded` ever fires — it's not nested inside + another callback already running on that same hook. + +- **If the plugin uses a singleton class already hooked to + `add_action('plugins_loaded', ['ClassName', 'instance'])`** (the + iWP Cache / iWP Elementor Addons / iWP Image Optimize shape, and + likely InformatiQ Toolkit's shape too — check for a + `public static function instance()` pattern), do **NOT** add a nested + `add_action('plugins_loaded', ..., 5)` inside that class's + constructor — confirmed dead code in exactly this shape, three times, + in one session. Instead call it **directly, immediately**, inside the + constructor: + ```php + private function __construct() { + new IWP_Updater( [ /* ... */ ] ); // direct call, no nested add_action + // ...rest of the constructor's hook registrations... + } + ``` + `IWP_Updater`'s own constructor only registers filters on OTHER hooks + (`pre_set_site_transient_update_plugins`, `plugins_api`, + `upgrader_process_complete`) — never `plugins_loaded` itself — so there + is no ordering reason to defer construction at all. Then add ONE settings field for the license key (option name matching `license_option` above) into wherever the plugin already has an admin