diff --git a/changelog.txt b/changelog.txt index f0a89d7..9974e95 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,109 @@ == Changelog == += 1.9.0 = + +_Release date: 2026-08-24_ + +**Highlights** + +* Action Scheduler is now bundled via Composer (`woocommerce/action-scheduler` 3.9.3): the external `action-scheduler` plugin is no longer required. + +**Added** + +* Bundled Action Scheduler library (production Composer dependency), loaded from the main plugin file and guarded so a standalone Action Scheduler plugin that loads first is respected. The library self-arbitrates when multiple plugins ship their own copy: only the newest registered version initializes. +* Header test `test_no_external_plugin_dependency` asserting the `Requires Plugins` header is gone, the main plugin file requires the bundled library, and composer.json declares it as a production dependency. + +**Changed** + +* `Requires Plugins: action-scheduler` removed from the plugin header and readme.txt; the plugin now works standalone. + +**Compatibility** + +* WordPress: 5.3 - 7.1 +* PHP: 8.0 - 8.5 +* WP-CLI: 2.x + +**Tests** + +* PHPUnit: 12 tests, 55 assertions + += 1.8.3 = + +_Release date: 2026-08-17_ + +**Highlights** + +* Plugin updates are now delivered through [Manager (by ROBOTSTXT)](https://www.robotstxt.software/plugins/robotstxt-manager/) instead of a bundled self-updater. + +**Added** + +* Dismissible admin notice on the site and Network Admin Plugins pages when Manager (by ROBOTSTXT) is not installed and active, linking to `https://www.robotstxt.software/plugins/robotstxt-manager/` (dismissible per user; re-appears if the Manager is later removed). +* Permanent (non-dismissible) notice on the site and network Settings pages while Manager is not available. +* `Update URI` plugin header pointing to `https://www.robotstxt.software/plugins/robotstxt-mediaaudit/`. +* Header tests now also cover `Plugin URI`, `Update URI`, `Author`, `Author URI`, `Contributors`, contributor ordering, and absence of self-updater artifacts. + +**Fixed** + +* The dismissible Manager notice now also renders on the Network Admin plugins page (screen base `plugins-network`), not only on site-level Plugins. +* `uninstall.php` now always removes the per-user notice-dismissal user meta (UI state, not user data). + +**Changed** + +* Plugin URI and Author URI now point to `www.robotstxt.software`; `robotstxt` is listed first in the Contributors headers. +* `bin/preflight.sh` allowlist updated: `update.json` and `robotstxt-updater.php` are forbidden in the release ZIP. +* Dev tooling updated via `composer update` (PHPCS 3.13.6, WPCS 3.4.1, PHPStan 2.2.8, PHPUnit 9.6.36); PHPStan fix for transient cache typing in the Network Tools status reader. +* Translations completed for Spanish (es_ES) and Catalan (ca): all fuzzy and untranslated strings through 1.8.2 are now translated (326/326 in both locales). + +**Removed** + +* Bundled self-updater files `robotstxt-updater.php` and `update.json`. + +**Compatibility** + +* WordPress: 5.3 - 7.1 +* PHP: 8.0 - 8.5 +* WP-CLI: 2.x + +**Tests** + +* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): PASS +* PHPStan level 9: PASS +* PHPCompatibility 8.0-8.5: PASS +* PHPUnit: 11 tests, 54 assertions + += 1.8.2 = + +_Release date: 2026-08-08_ + +**Changed** + +* Contribution guidelines restructured: `AGENTS.md` is now a slim index pointing to topic-specific `AGENTS-*.md` files (single source of truth — the security checklist and the audit prompt template each live in exactly one canonical file). +* `bin/deploy.sh` now reads its release exclude list from a shared `bin/dist-exclude.txt` so staging and verification cannot drift apart. + +**Added** + +* `bin/preflight.sh` automated pre-deploy verification script (PHPCS, PHPStan level 9, PHPCompatibility, PHPUnit, `composer audit`, candidate-ZIP artifact inspection). +* Plugin header tests (`tests/`) validating required/forbidden headers and `Stable tag` ↔ `Version` ↔ `ROBOTSTXT_MEDIAAUDIT_VERSION` consistency. +* `.claude/settings.json` deny rules mechanically enforcing the no-auto-deploy / no-push / no-tag / no-merge boundary. +* `.claude/agents/security-auditor.md` (read-only pre-deploy audit subagent) and `.claude/commands/deploy-check.md` slash command. + +**Fixed** + +* `bin/deploy.sh` now excludes all `AGENTS-*.md` dev docs from the release ZIP; previously only `AGENTS.md` was excluded, so the split topic files would have leaked into the distribution. + +**Compatibility** + +* WordPress: 5.3 - 7.1 +* PHP: 8.0 - 8.5 +* WP-CLI: 2.x + +**Tests** + +* PHPCS (WordPress-Core, WordPress-Docs, WordPress-Extra): PASS +* PHPStan level 9: PASS +* PHPCompatibility 8.0-8.5: PASS +* PHPUnit: 8 tests, 41 assertions + = 1.8.1 = _Release date: 2026-06-08_ diff --git a/includes/Admin/ManagerNotice.php b/includes/Admin/ManagerNotice.php new file mode 100644 index 0000000..2be289d --- /dev/null +++ b/includes/Admin/ManagerNotice.php @@ -0,0 +1,209 @@ +base, array( 'plugins', 'plugins-network' ), true ) ) { + return; + } + + if ( self::is_manager_active() ) { + return; + } + + if ( ! current_user_can( 'install_plugins' ) ) { + return; + } + + if ( '1' === get_user_meta( get_current_user_id(), self::DISMISS_KEY, true ) ) { + return; + } + + $dismiss_url = wp_nonce_url( + add_query_arg( self::DISMISS_ARG, 1 ), + self::DISMISS_ACTION + ); + + echo '

' + . wp_kses( self::message(), array( 'a' => array( 'href' => true ) ) ) + . ' ' . esc_html__( 'Dismiss', 'robotstxt-mediaaudit' ) . '' + . '

'; + } + + /** + * Renders the permanent inline notice for the Settings pages (not dismissible). + * + * Used on both the site Settings page and the Network Settings page. + * + * @return void + */ + public static function render_settings_notice(): void { + if ( self::is_manager_active() ) { + return; + } + + echo '
' + . '

' . wp_kses( self::message(), array( 'a' => array( 'href' => true ) ) ) . '

' + . '
'; + } + + /** + * Handles the dismiss link: verifies the nonce, stores the dismissal (PRG). + * + * Also clears a stored dismissal once Manager is active again, so the + * notice returns if Manager is ever removed. + * + * @return void + */ + public static function handle_dismiss(): void { + $user_id = get_current_user_id(); + $dismissed = $user_id && get_user_meta( $user_id, self::DISMISS_KEY, true ); + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- presence check only; state changes require the nonce below. + if ( ! isset( $_GET[ self::DISMISS_ARG ] ) && ! $dismissed ) { + return; + } + + if ( self::is_manager_active() ) { + if ( $dismissed ) { + delete_user_meta( $user_id, self::DISMISS_KEY ); + } + return; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no state change without the nonce check below. + if ( ! isset( $_GET[ self::DISMISS_ARG ] ) ) { + return; + } + + if ( ! current_user_can( 'install_plugins' ) ) { + return; + } + + check_admin_referer( self::DISMISS_ACTION ); + + update_user_meta( $user_id, self::DISMISS_KEY, '1' ); + + wp_safe_redirect( remove_query_arg( array( self::DISMISS_ARG, '_wpnonce' ) ) ); + exit; + } + + /** + * Builds the shared notice message (translation with a link placeholder). + * + * The result must be passed through wp_kses() at output time. + * + * @return string Unescaped message HTML. + */ + private static function message(): string { + return sprintf( + /* translators: %s: Manager plugin page URL. */ + __( 'To receive plugin updates, the plugin Manager (by ROBOTSTXT) must be installed and active.', 'robotstxt-mediaaudit' ), + esc_url( self::MANAGER_URL ) + ); + } +} diff --git a/includes/Admin/Settings.php b/includes/Admin/Settings.php index a395b77..230d9f0 100644 --- a/includes/Admin/Settings.php +++ b/includes/Admin/Settings.php @@ -963,6 +963,8 @@ class Settings {

+ +