Move template-tags.php and deprecated.php under includes/ They were the only loose PHP left in the plugin root, which is inconsistent with the rest of the collection: wp-useronline keeps both under includes/ (638001f), and wp-pagenavi and wp-polls do the same with template-tags.php. Only the three files WordPress requires to be there stay at the root -- the main plugin file, uninstall.php, and the index.php silence guard. Adds a test pinning that layout, so the next file cannot quietly land in the root again. Verified after the move: 115 tests single-site and multisite, the Playground harness 84/84 on PHP 7.4 / WP 6.0 and PHP 8.3 / WP latest, and a deploy dry-run confirming the shipped tree is the same 12 files in their new places. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo
Recent Commits to wp-pluginsused:master
-
Move template-tags.php and deprecated.php under includes/
-
Cover multisite in CI and bin/test.sh
Cover multisite in CI and bin/test.sh The suite passed as multisite already; nothing was pinning it that way, so nothing would have caught a regression. Two behaviours only exist on a network: uninstall.php branches on is_multisite(), and network-activated plugins are read from active_sitewide_plugins, a site option the single-site run never touches. Running as a network also makes the uninstall test execute the get_sites() loop and the switch_to_blog()/restore_current_blog() pair for real rather than only asserting on the source. WP_MULTISITE is read by the WordPress test bootstrap inside the container, so it is forwarded in rather than exported around the npx process. 114 tests / 220 assertions green single-site and multisite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo
-
Run CI on Node 24
Run CI on Node 24 Node 20 went end-of-life in April 2026. This standardises every plugin on 24, the current active LTS (Krypton), which wp-polls was already using. This is the Node that runs npx @wordpress/env for the test suite, not the Actions runtime -- that was the separate checkout/setup-node v7 bump -- so it had to move on its own.
-
Bump actions/checkout and actions/setup-node to v7
Bump actions/checkout and actions/setup-node to v7 GitHub is deprecating Node 20 on Actions runners, so checkout@v4 and setup-node@v4 were being force-run on Node 24 and emitting a warning on every job. v5 moved these actions to Node 24; v7 is the current major. Neither breaking change affects this workflow: setup-node v6 limited automatic caching to npm, and no cache input is passed here; checkout v7 blocks checking out fork PRs for pull_request_target and workflow_run, and this triggers on push and pull_request. shivammathur/setup-php stays at v2 -- that is still its current major and it was not in the deprecation warning.
-
Expand the test suite from 67 to 114 tests
Expand the test suite from 67 to 114 tests New coverage: - test-bootstrap.php: constants agree with the plugin header and the readme Stable tag, every code file carries its ABSPATH guard, the classes avoid the WP_ prefix core reserves, and the admin hooks are absent on the front end. - test-stats.php: the summary sentence pluralises through three independent _n() calls, so singular, plural and zero are three code paths. None were covered; the singular form had never been rendered by a test. - Uninstall is now executed, not just read: it deletes the option for real on the single-site branch. Added checks that it references no plugin class or function (it runs with the plugin inactive) and that the plugin writes no option row other than the one uninstall names. - Template: hiding matches whole names rather than substrings, no trailing space when the version is suppressed, absent URIs emit no anchor, descriptions are still texturized, the per-request cache holds and reset_cache() clears it, icons carry role/aria-label and differ by shape, and nothing enqueues a script or stylesheet. - Settings: a plugin name containing quotes round-trips through the form and still matches what it hides, unknown keys are dropped, ticked boxes render as checked. - Deprecated: the render path must never call a shim, or every page load would emit deprecation notices under WP_DEBUG. Two harness bugs the new tests exposed, both fixed: - GLOB_BRACE is a GNU extension and is undefined in the container's PHP, so three tests errored. Source files are now collected with two glob() calls. - Searching raw source matched comments, not code: the docblock in class-pluginsused-template.php explaining that pluginsused_format_display() forwards to it counted as a call site. Source inspection now goes through php_strip_whitespace(), the same fix the uninstall test already needed. phpcs.xml needed no new exemptions. helper-source.php is split out of helper-fixtures.php because a file may declare either functions or a class. 114 tests / 220 assertions green on PHP 7.4 / WP 6.0 and PHP 8.3 / WP latest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo
-
Add PHPUnit suite, CI, and update the readme for 2.0.0
Add PHPUnit suite, CI, and update the readme for 2.0.0 67 tests / 122 assertions against a real WordPress database via wp-env, run on both ends of the support matrix (PHP 7.4 / WP 6.0 and PHP 8.3 / WP latest). Nothing here ships: plugin_deploy.sh already excludes tests/, bin/, phpunit.xml*, composer.*, .wp-env.json and .git*. The tests drive the plugin's own entry points against fixture plugins written to disk, because every defect worth catching here is integration, not logic: - test-escaping.php parses the output with DOMDocument and asserts no event-handler attribute and no javascript: URL survives into the DOM, while the hostile name is still displayed as text. Substring matching would be wrong: once escaped, "onmouseover="" still occurs inside an attribute value, where it is inert. - test-uninstall.php asserts on php_strip_whitespace() output rather than the raw file. Matching the raw source passes on the comment that documents 'number' => 0 and keeps passing after the argument itself is deleted -- confirmed by deleting it. - test-template.php covers the multisite active_sitewide_plugins fix, ordering, and that hidden plugins leave the counts as well as the listings. Every assertion was verified to fail against deliberately broken code. phpcs.xml relaxes docblock, file-naming and DOM-property rules under tests/ only, so CI can fail on warnings everywhere else. Adds .gitignore: wp-env installs dev dependencies into the mounted plugin directory, so vendor/ appears on the host and must not be committed. Readme: documents the settings screen, the three filters and the template tag, trims Tags to the five-tag maximum, drops the dead dev.wp-plugins.org translations link and the FamFamFam credit for the removed GIFs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo
-
Restructure into includes/, add a settings screen, drop the GIF icons
Restructure into includes/, add a settings screen, drop the GIF icons Replaces the hand-rolled plugin scanner and the edit-the-source config with current WordPress APIs. Behaviour is unchanged: the three shortcodes, the display_pluginsused() template tag and the rendered markup all keep working. - Scanning: get_plugins() replaces the bespoke directory walk and the "|Plugin Name:(.*)|i" header regexes, which read whole files and could match a "Version:" occurring anywhere in one. Core sorts identically (_sort_uname_callback is the same strnatcasecmp on name), so ordering is byte-for-byte what it was. - Config: settings now live in one option row, pluginsused_options, behind Settings -> WP-PluginsUsed. Previously the only way to hide a plugin or a version number was to edit wp-pluginsused.php, which every plugin update silently reverted. The PLUGINSUSED_SHOW_VERSION constant and the $pluginsused_hidden_plugins global are still honoured, and two new filters (pluginsused_show_version, pluginsused_hidden_plugins) are applied last. - Icons: inline SVG replaces images/plugin_{active,inactive}.gif. They stay crisp at any pixel density, inherit the theme's text colour, cost no HTTP request, and distinguish state by shape as well as by an aria-label. - Multisite: network-activated plugins were listed as inactive, because only active_plugins was consulted. active_sitewide_plugins is now merged in. - Empty Plugin URI / Author URI no longer emit <a href="">. - Drops load_plugin_textdomain(); WordPress has loaded plugin translations automatically since 4.6, and since 6.7 an early call triggers _doing_it_wrong. - Old procedural functions remain in deprecated.php as working shims. Layout follows the Plugin Handbook: includes/class-*.php, index.php silence guards, an uninstall.php whose multisite loop passes 'number' => 0 and restores inside the loop. Verified with a WordPress Playground harness, 84 assertions, on both PHP 7.4 / WP 6.0 and PHP 8.3 / WP latest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo -
Apply WordPress Coding Standards (phpcbf)
Apply WordPress Coding Standards (phpcbf) Formatting only, no logic change: 96 violations fixed automatically. Adds phpcs.xml so the ruleset is pinned to PHP files (WPCS has had no JavaScript sniffs since 3.0) and CI uses the same config as local runs. Verified against the Playground harness: 33/33 assertions unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo
-
Fix stored XSS in plugin listing output
Fix stored XSS in plugin listing output pluginsused_format_display() interpolated plugin header values straight into href/src/alt/title attributes. strip_tags() was the only filtering applied, and it removes tags but leaves quotes, so a plugin whose header contains a double quote broke out of the attribute: Plugin Name: Evil" onmouseover="alert(1) Plugin URI: javascript:alert(2) rendered as a live onmouseover handler on both the <img> and the <a>, plus href="javascript:alert(2)". Escape at each sink with esc_url(), esc_attr() and esc_html() instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TFsMxKw9RRmE5Kk41hAxYo