Emit the markup kses leaves, and assert loss rather than bytes The plugin has two ways out: the shortcodes return their markup, while display_pluginsused() echoes it through wp_kses(). kses normalises as well as filters, so the two paths were emitting different bytes for the same listing - viewBox lowercased to viewbox, a space added before a self-closing slash, ' rewritten to ' inside a title. A theme calling the template tag got markup the shortcode never produced. The icon now spells its attributes the way kses leaves them, so the two paths agree. Lowercase viewbox is safe and is what the echoing path has always shipped: an HTML parser applies its SVG case-fixup table, so it reaches the DOM as viewBox and the icon still scales. The entity rewriting cannot be pre-empted that way, so the two tests now assert what they mean rather than byte-identity. The allow-list test compares inventories of tags and attribute names, since an incomplete allow list drops a tag or an attribute and that is the failure worth catching. The template-tag test compares the echoed output against wp_kses( render(), allowed_html() ), which is what it should equal. Also made the settings_fields() assertion quote-agnostic: core quotes option_page with single quotes, which is not part of any contract. 158 tests, 442 assertions, green.
Recent Commits to wp-pluginsused:master
-
Emit the markup kses leaves, and assert loss rather than bytes
-
Match wp_enqueue_script( with its bracket, not as a substring
Match wp_enqueue_script( with its bracket, not as a substring The no-jQuery test forbade the bare string 'wp_enqueue_script', which is also a substring of the ACTION name wp_enqueue_scripts - the hook a plugin legitimately uses to enqueue a stylesheet. Registering no scripts is not the same as never touching the hook scripts are registered on. wp-stats failed on exactly this: it hooks wp_enqueue_scripts to add its one sheet. The others here pass today only because they happen not to, so this is a latent false failure waiting for the first stylesheet. Found by the first PHPUnit run of this work.
-
Re-lock composer against the PHP 8.2 floor
Re-lock composer against the PHP 8.2 floor composer.json moved to >=8.2 with the floor raise but the lock file still recorded >=7.4. Composer treats that as an error rather than a warning, so 'composer install' would have failed inside the tests container before PHPUnit ran a single test - and CI does exactly that on all six matrix rows. bin/verify.py now compares the two, so the pair cannot drift again.
-
Step 12: drop the last two phpcs suppressions and fix the code under …
Step 12: drop the last two phpcs suppressions and fix the code under them Both were in includes/, both had a reason written next to them, and the target for this collection is zero. display_pluginsused() echoed its markup behind an EscapeOutput ignore. It now escapes at the sink like everything else, through wp_kses() with an allow-list that says exactly which tags and attributes the listings use. A test asserts wp_kses() leaves all three listings byte-identical, so the day the markup grows a tag the list has not got, the suite says so rather than the template tag quietly printing less than the shortcodes do. The summary sentence carried an I18n.NoHtmlWrappedStrings ignore arguing that rewording the msgid would orphan its translations. That is true and it is still the right trade: a translator has no business being handed <strong>, and a msgid wrapped in a tag is a translation waiting to lose it. The tags moved into the concatenation, the rendered string is byte-for-byte what it was, and the three orphaned msgids are recorded as a NOTE in the changelog. phpcs and php -l are clean across the plugin, and bin/verify.py reports zero.
-
Step 11: put the suite on the house conventions and add the shared tests
Step 11: put the suite on the house conventions and add the shared tests Files and classes follow 7.1: helper-fixtures.php becomes helper-testcase.php because what it declares is the base test case, test-stats.php becomes test-summary.php so nothing confuses it with the WP-Stats integration this plugin does not have, and every class is WP_PluginsUsed_<Area>_Test extending WP_PluginsUsed_TestCase. test-bootstrap.php and test-uninstall.php extended WP_UnitTestCase directly and now go through the shared base like the rest. Discovery is by the test- prefix now, so a misnamed file would be silently skipped rather than fail: 109 test methods before this step, 150 after, none lost in the rename. tests/test-metadata.php carries the fourteen shared assertions from 7.2, plus three specific to what this plugin just did: that every fired hook carries the prefix, that no apply_filters_deprecated shim crept back in, and that the Upgrade Notice names all three renamed filters in both spellings. The last one is the guard that matters -- the rename is only survivable for a site owner if the notice actually lists it. run_uninstall() is shared rather than repeated, because uninstall.php declares a function and so can only be included once per process. Two files assert against it; the second include would have been a silent no-op and the test would have passed on rows nothing had deleted.
-
Step 2 (redo): re-sync the shared config files after the templates moved
Step 2 (redo): re-sync the shared config files after the templates moved _standards/templates/phpcs.xml and phpunit-multisite.xml.dist changed while this plugin was being worked through, so the copies taken at 516e33a had drifted from the shared originals. Both are re-copied verbatim. Neither new exclusion applies here -- WP-PluginsUsed owns no database table and has no list table -- but the point of these files is that all nineteen carry the same one, so it is copied whole rather than trimmed to fit.
-
Step 10: put the readme on the canonical section set
Step 10: put the readme on the canonical section set The six h2s in the order the standard fixes them: Description, Usage, Frequently Asked Questions, Screenshots, Changelog, Upgrade Notice. Usage was an h3 buried in Description and the changelog sat above the screenshots. The License line gets its two trailing spaces back. Without them Markdown ran "License: GPLv2 or later" and the License URI together into one line on GitHub, which is invisible in the source and in a diff. Description gains a Features list and keeps Donations as its last h3, in the one wording all nineteen use, without the stray bullet it carried. Development and Credits are gone: the repo link belongs in the repo, and the Flaticon credit was for the wordpress.org listing icon rather than anything this plugin ships -- worth re-adding to the .org assets if it is still owed. Changelog entries are sorted into BREAKING, NEW, CHANGED, FIXED, NOTE within 2.0.0. The security fix moves from a sixth prefix, SECURITY:, to FIXED:, the raised floors become their own BREAKING line, and four 2008-era bullets that were indented with two spaces are normalised so they parse as bullets at all.
-
Step 9: scope the emitted class names and drop the last inline style
Step 9: scope the emitted class names and drop the last inline style The plugin ships no stylesheet, but it does emit markup, and the two class names on the state icons were unscoped. They become wp-pluginsused-icon, wp-pluginsused-icon-active and wp-pluginsused-icon-inactive, so a theme has one prefix to target and no chance of colliding with anything else. The SVGs lose their style="vertical-align: middle" as well. It was the last inline CSS in the plugin, and the icons still inherit their colour through currentColor, which is the part that actually matters for a theme. There is no css/ or js/ directory to normalise -- the plugin has neither, and the SVG is built in PHP -- so this is the whole of steps 7 to 9. Steps 7 and 8 are no-ops: no JavaScript, and the GIF markers were already replaced by inline SVG earlier in this major.
-
Step 6: put the settings screen on the house rules
Step 6: put the settings screen on the house rules The screen was already Settings API rather than hand-written markup, so this is the naming and the details around it: * CAPABILITY, SECTION_DISPLAY and SECTION_HIDDEN join PAGE and GROUP, and the two section ids and both field ids gain the plugin's prefix. * Every capability check goes through capability(), which fires wp_pluginsused_capability with a context, so a site hands the screen to a different role in one place rather than by hunting current_user_can() calls. * register() and render() become register_settings() and render_page(), matching the other settings screens in the collection. * The hidden-plugins list loses its inline style attribute and separates its labels with <br />, the way core's own checkbox lists do. That was the only inline CSS in the plugin. Five tests follow, including two standing guards: no inline style attribute reaches the screen, and no file hand-writes a form table.
-
Step 5: prefix the three public filters and write the Upgrade Notice
Step 5: prefix the three public filters and write the Upgrade Notice pluginsused_show_version -> wp_pluginsused_show_version pluginsused_hidden_plugins -> wp_pluginsused_hidden_plugins pluginsused_plugins_used -> wp_pluginsused_plugins_used All three shipped in 1.50, so this genuinely breaks third-party code. The old names are dropped outright -- no apply_filters_deprecated() shims, which was the decision taken for the whole collection: a shim keeping an unprefixed name alive keeps the clash the rename exists to remove. Two more unprefixed identifiers go with them, for the same reason and in the same release rather than one at a time: the PLUGINSUSED_SHOW_VERSION constant and the $pluginsused_hidden_plugins global, which were how 1.50 was configured by editing the plugin's own file. The settings screen added in this major does both jobs and survives updates, which is what those two never did. The Upgrade Notice is written for a site owner: what silently stops working, where in their site to look for it, what the symptom will be weeks later, and the reassurance that no code of their own means nothing to do. The changelog carries the same two BREAKING lines in developer shorthand. Tests follow: the filter call sites are renamed, and the two that asserted the legacy global and constant still worked now assert that they do not. -
Step 4: give the plugin the two canonical option rows
Step 4: give the plugin the two canonical option rows Settings move from pluginsused_options to wp_pluginsused_options, and the version markers get a row of their own, wp_pluginsused_version, holding 'plugin' and 'db' and nothing else. Keeping them apart means the settings screen and the upgrade routine can never overwrite each other, and the sanitise callback never has to rescue a value the form did not post. There was no migration here to retarget -- 1.50, the last release, stored nothing at all, so the unprefixed row only exists on an install that ran a development build of 2.0.0. maybe_upgrade() is that one migration: it folds the old row in, re-sanitises it, deletes it, then stamps both markers in a single write so a half-finished upgrade cannot record itself as complete. It runs on activation and on every admin load, because activation hooks do not fire on update, which is the usual reason a migration never runs. uninstall.php removes all three rows -- both current ones and the unprefixed leftover, for an install removed before it ever reached wp-admin. Seven new tests cover the fold, the re-sanitise, the refusal to overwrite a newer prefixed row, idempotency, and the shape of the marker row.
-
Step 3: prefix the classes, add the six constants, raise the floors
Step 3: prefix the classes, add the six constants, raise the floors PluginsUsed, PluginsUsed_Options, PluginsUsed_Settings and PluginsUsed_Template become WP_PluginsUsed_*, each in the class-wp-pluginsused-* file its name now implies. Nothing unprefixed is left in the global namespace except the template tag and the deprecated shims, which are the documented public API and keep the names they shipped with. The plugin file gains the four constants it was missing -- DB_VERSION, SLUG, DIR and URL -- and loads includes/ through DIR rather than __DIR__. Floors go to WordPress 6.8 and PHP 8.2 in the plugin header and the readme; phpcs.xml, .wp-env.json, composer.json and the CI matrix already carried them from the templates. There is no version-guarded back-compat shim to delete: the plugin has never branched on a PHP or WordPress version. The one test that asserted the classes must NOT be prefixed with WP_ now asserts the opposite, and that each one lives in the file its name implies.
-
Step 2: adopt the shared config files from _standards/templates/
Step 2: adopt the shared config files from _standards/templates/ .editorconfig, .gitignore, composer.json, phpcs.xml, both phpunit configs, bin/test.sh, bin/test-multisite.sh, every index.php and ci.yml now come from the templates verbatim, with only the placeholders substituted. The eslint job goes with them: this plugin ships no JavaScript, so there is nothing to lint and no package.json, eslint.config.mjs or vitest.config.mjs to add. .wp-env.json gains the ports assigned in STANDARDS.md 10, 8906 and 8907, so this plugin's environment can no longer collide with a sibling's. The template's /tests/* exclusion list is closed, which cost four fixes here rather than four new exclusions: * The fixtures write their plugin files through WP_Filesystem instead of mkdir()/file_put_contents()/unlink()/rmdir(), with the transport pinned to "direct" in the bootstrap so it never negotiates credentials. The two tests that built a fixture inline now share the same pair of helpers. * The DOM inspection reads DOMAttr's lowercase name/value and describes the node with getNodePath(), rather than reaching for camelCase DOMNode properties WPCS cannot let through. * Five docblocks opening on a lowercase function name were reworded.