Skip to content

Prepare the template for plugins that succeed a 1.x release - #21

Merged
ahegyes merged 3 commits into
trunkfrom
feat/peer-getters
Oct 29, 2026
Merged

ahegyes merged 3 commits into
trunkfrom
feat/peer-getters

Conversation

@ahegyes

@ahegyes ahegyes commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Summary

Three changes that a plugin succeeding a 1.x release needs, one commit each:

  • a peer plugin had no supported way to read the plugin's slug, name or version, short of the Plugin class or parsing the header;

  • uninstall.php named the main file by its source name, so a release shipping it under another name would run the floor check on a missing file, whose empty floors pass;

  • nothing told such a plugin how to keep shipping its 1.x main file's name.

  • Peer getters: includes/metadata.php adds dws_tpl_get_plugin_slug(), dws_tpl_get_plugin_name() and dws_tpl_get_plugin_version().

    • Each reads only the plugin's header through get_file_data(), so a getter that a partial deploy has defined still answers when a later include throws.
    • The name is translated through __() once init has fired, and keeps an & as written, which get_plugin_data()'s kses pass would encode.
    • AGENTS.md's add-on convention says to call them from plugins_loaded on, since an add-on may load first, and behind function_exists(), since a base plugin below its floors or with a partial build may not define them. AGENTS.md's layout, docs/ARCHITECTURE.md and the tests README name the file.
  • Uninstall: uninstall.php takes the one root *.php in its folder that carries a Plugin Name header. WP_UNINSTALL_PLUGIN cannot name it, because a bulk uninstall (delete_plugins(), wp plugin uninstall a b) defines it once, for the first plugin it visits. With none or several header files, the uninstall keeps the data.

  • Recipe: the wordpress.org recipe gains a step that passes the 1.x name as the release's archive-entry-file (Let a release ship the main plugin file under another name wordpress-configs#58), with its reason comment. It also says why the rename suits only wordpress.org distribution: the GitHub updater reads the next release's floors from the main file at the release tag, under its installed name.

Code +68 −1 (2 files), tests +92 −1 (4 files), docs +10 −3 (3 files).

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Build / CI / tooling
  • Documentation
  • Dependency update

Checklist

  • Changelog fragment added (composer changelog:add): none; the v2 rebuild ships as "Initial release."
  • Tests added/updated:
    • MetadataTest: the slug and version against what WordPress lists, the name translated after init with an & in the translation, and the name untranslated before init;
    • PartialDeployTest: with src/Plugin.php hidden, options.php throws after metadata.php has loaded, and the getters still answer in that request;
    • UninstallTest: with WP_UNINSTALL_PLUGIN naming another plugin and the main file renamed under a PHP floor no site meets, the uninstall logs that the site is below its floors.
  • composer quality-check passes locally
  • Plugin header updated if runtime requirements changed: no runtime requirement changed

Breaking changes for forks

  • Uninstall: a fork that ships a second root PHP file with a Plugin Name header keeps its data on uninstall, since the main file cannot be told apart.

Review points.

  • Configs pins: the template still pins configs at 3cbd985, which lacks archive-entry-file. Move the wordpress-configs pins to its current trunk #22 moves the pins to the merge of Let a release ship the main plugin file under another name wordpress-configs#58 and drops the recipe step's "from a wordpress-configs version that has the input".
  • Changed files in the checkout: the new PartialDeployTest and UninstallTest rows rename src/Plugin.php or the main file for one request or process and restore them in finally. A run killed mid-row can leave a .hidden file or a bootstrap.php behind.
  • GitHub distribution: a GitHub-distributed plugin that renames its main file in the archive is never offered another release; the recipe limits the rename to wordpress.org distribution rather than changing the updater.

Testing.

  • composer quality-check passes: validate, changelog validation, PHPCS on both profiles, PHPStan, Unit 6 and Integration 81.
  • npm run test:e2e passes (3 of 3), and lint:scripts, lint:styles and lint:types are clean. includes/metadata.php and uninstall.php parse on PHP 7.4.
  • Before the fixes: every new row failed: the getters were undefined, the translated name came back as &, the slug getter fataled with src/Plugin.php missing, and the uninstall read its floors from the source name, then from the other plugin's file.

A peer plugin had no supported way to read the plugin's slug, name or
version. The slug lived only on Plugin::SLUG and the version only in the
header, so a peer had to reach into the composition root's class or
parse the header itself, which ties it to code the plugin is free to
change. A plugin that succeeds a 1.x release also needs a home for that
release's name and version functions, which peer plugins call.

includes/metadata.php defines dws_tpl_get_plugin_slug(),
dws_tpl_get_plugin_name() and dws_tpl_get_plugin_version(). Each reads
only the header through get_file_data(), the slug as its Text Domain,
so a getter that a partial deploy has already defined still answers
when a later include throws, which a PartialDeployTest row checks. The
name is translated only once init has fired, because WordPress flags
translations loaded earlier, and through __() rather than
get_plugin_data(), whose kses pass turns an & into &. AGENTS.md
and docs/ARCHITECTURE.md name the file, the tests README's Integration
row names the getters, and the add-on convention tells an add-on to call
them from plugins_loaded on, behind function_exists().

Assisted-by: Claude Code:claude-opus-5-5
uninstall.php named the main file by its source name. A release that
ships the main file under another name, as a plugin keeping its 1.x
main file must, would leave the floor check reading a file that does
not exist: get_file_data() then returns empty floors, which pass, so
the uninstall engine would run on a site below the plugin's floors and
be handed a missing plugin file. WP_UNINSTALL_PLUGIN cannot stand in
for the name, because a bulk uninstall defines it once, for the first
plugin it visits.

uninstall.php takes the one PHP file in its folder that carries a
Plugin Name header, and keeps the data when it finds none or several.
An UninstallTest row runs the script with WP_UNINSTALL_PLUGIN naming
another plugin and the main file renamed under a PHP floor no site
meets, and expects the below-floor log line.

Assisted-by: Claude Code:claude-opus-5-5
A plugin that succeeds a 1.x release whose main file was bootstrap.php
must keep shipping that file, or the update deactivates it on every
site. Nothing in the template said how, and the release workflow is one
the fill-in never edits.

The wordpress.org recipe gains a step that passes the old name as the
release's archive-entry-file, with the comment its caller carries, and
says why the rename suits only wordpress.org distribution: the GitHub
updater reads the next release's floors from the main file at the tag,
under its installed name.

Assisted-by: Claude Code:claude-opus-5-5
@ahegyes ahegyes changed the title Feat/peer getters Prepare the template for plugins that succeed a 1.x release Oct 29, 2026
@ahegyes
ahegyes merged commit 2548e43 into trunk Oct 29, 2026
31 checks passed
@ahegyes
ahegyes deleted the feat/peer-getters branch October 29, 2026 19:24
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