== Changelog ==

= 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_

**Fixed**

* Network Operations buttons now use standard HTML POST forms with server-side redirect (PRG pattern), replacing the AJAX approach that failed due to network admin script-loading constraints.
* Removed unreliable in-render script enqueueing for Network Tools page; operation buttons now work without JavaScript.
* JS dismiss/cancel labels on the Alerts page now use i18n strings from `wp_localize_script`.

= 1.8.0 =

_Release date: 2026-06-08_

**Added**

* **WordPress Multisite support** — plugin now declares `Network: true` and can be activated network-wide.
* **Two operating modes** configurable from Network Admin → Media Audit → Settings:
  * **Per site** (default) — each site operates independently, identical to v1.7.x behaviour.
  * **Central** — API credentials and filter lists are configured once in the network admin and shared across all sites. Site-level admin pages show a placeholder notice linking to the network admin panel.
* **Network admin panel** (central mode only): aggregated Audit list, Alerts list, and Tools page showing all media across all sites with a "Site" column identifying origin. Network Tools page allows triggering internal and external scans across all sites via Action Scheduler.
* **Network mirror table** `{base_prefix}mra_network_index` — lightweight denormalised index enabling fast cross-site queries without per-request `switch_to_blog()` loops. Synchronised automatically after each scan batch.
* **New site provisioning** — when the plugin is network-active, tables are automatically created for newly added sites via the `wp_initialize_site` hook.
* **Settings migration** — on first switch to central mode, existing settings from the main site are copied to the network option as a starting point.
* **Network-aware uninstall** — when `delete_on_uninstall` is set, uninstalling removes tables and options from every site in the network plus the mirror table.
* DB schema version bumped to `1.3.0`.

= 1.7.1 =

_Release date: 2026-06-02_

**Fixed**

* Dismissal timestamps are now stored in UTC, consistent with all other plugin timestamps (`dismissed_at`).
* Settings option (`robotstxt_mediaaudit_settings`) no longer autoloads on every page request, reducing the autoloaded options payload.
* Alert count on the Media Audit dashboard is now cached with a transient (1 hour TTL) and invalidated automatically on dismiss, reactivate, scan, purge, and full reset operations.
* "Dismiss" and "Cancel" button labels on the Alerts page are now translatable via `wp_localize_script`.
* Added direct file access guard (`if ( ! defined( 'ABSPATH' ) ) { exit; }`) to all PHP class files in `includes/`.

= 1.7.0 =

_Release date: 2026-05-11_

**Added**

* New **Alerts** admin page (Media Audit → Alerts) with two tabs:
  * *Active Alerts* — lists all attachments with active (non-dismissed) alert-domain matches. Each row has a "Dismiss" button that expands an inline form to add optional notes.
  * *Dismissed Alerts* — lists alerts that have been manually dismissed, with the dismissal notes, who dismissed them, and when. Each row has a "Reactivate" button to restore the alert.
* New DB table `mra_dismissed_alerts` (schema migration 1.2.0): stores attachment ID, dismissed-by user ID, timestamp, and free-text notes (up to 1,000 characters).
* Dashboard "Alert" card now counts only active (non-dismissed) alerts.
* Alert filter in the Media Audit list now excludes dismissed alerts.
* External Status column shows a grey "Dismissed" badge instead of the red "Alert" badge for dismissed attachments.
* "Export CSV (Alerts only)" bulk action now excludes dismissed alerts. Both CSV exports include new "Dismissed" and "Dismissed Notes" columns.
* `top_domains` stored per provider raised from 10 to 50 entries to prevent low-frequency alert domains from being silently discarded.

**Fixed**

* Alert filter in Media Audit list was incorrectly restricted to `external_status = 'matches'`, causing it to miss attachments whose alert domains were found but whose aggregate status had not yet been updated to `matches`. The filter now correctly matches the dashboard card count.
* Capability checks in `AuditPage` (render, AJAX handler, CSV export, bulk actions) used the weaker `edit_posts` capability instead of the `edit_others_posts` used in the menu registration.

**Schema changes:** `ROBOTSTXT_MEDIAAUDIT_DB_VERSION` → `1.2.0`

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.5
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.5
* PHPUnit: 48 tests, 78 assertions

= 1.6.0 =

_Release date: 2026-05-05_

**Added**

* Settings page reorganised into four native WordPress tabs: General, API Credentials, Filters, External Scanning. Each tab saves independently via a hidden `_tab` sentinel field, preventing cross-tab data loss.
* Filters tab: two configurable hostname lists — "Alert Hostnames" (domains that should trigger a copyright-risk alert) and "Ignored Hostnames" (domains to suppress from results). Wildcard notation `*.example.com` matches the apex domain and all subdomains.
* Auto-normalization on save: scheme, path, query, fragment, port, and `www.` prefix are stripped; duplicates removed; list sorted alphabetically.
* Default filter lists seeded on first activation (idempotent — only runs when the option key does not yet exist): 60 stock-agency / press-agency alert domains, 56 social-media / CDN ignored domains.
* `HostnameFilter` utility class (`includes/External/HostnameFilter.php`): static methods `is_alert()`, `is_ignored()`, `classify()` (`alert`|`ignored`|`other`), and `has_alert_domains()`. Domain classification is request-cached.
* Dashboard stats strip: new "Alert" card showing the count of attachments that have at least one alert-domain match across all external providers.
* Audit list: "Alert" option added to the External Status filter dropdown; when selected, PHP-side filtering classifies each attachment's `top_domains` and returns only those with at least one alert domain.
* Audit list: Alert badge in the External Status column for attachments with alert-domain matches; badge label shows the count of non-ignored matching domains.
* Quick-view modal: ignored domains are now hidden entirely; alert domains are highlighted with an Alert status badge in the domain table.
* Full Report attachment detail page: external results per provider split into three labelled sections — Alert domains, Other domains, and Ignored domains.
* Unified CSV export replaces the two previous separate exports:
  * "Export CSV" — all indexed attachments, one row per attachment, containing: ID, filename, URL, MIME type, file size, internal scan date, external status, has alert (Yes/No), usage count, used-in post titles, Google Vision match count, TinEye match count, PicDefense match count, alert domains, other domains, ignored domains.
  * "Export CSV (Alerts only)" — same format and columns, filtered to attachments with at least one alert domain match.
* "Media Audit" top-level admin menu repositioned to appear immediately below the built-in Media menu (WordPress admin menu position 11).

**Removed**

* Previous "Export External Results CSV" bulk action replaced by the unified export above.

**No schema changes:** `ROBOTSTXT_MEDIAAUDIT_DB_VERSION` remains `1.1.0`.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.5.0 =

_Release date: 2026-05-05_

**Added**

* PicDefense as a third external scan provider: POST-based copyright risk analysis via `POST /checkImageRisk`, authenticated with `X-API-TOKEN: {userId}:{apiKey}` header.
* PicDefense results include risk classification (`picrisk`: high/medium/low), backlinks with similarity score and image URL, risk flags (face, logo, landmark, stock, EXIF copyright), and label detection.
* Full Report attachment detail page: PicDefense section with picRisk badge, risk flags list, backlinks table sorted by similarity score (highest first), and detected labels.
* Audit list quick-view modal: picRisk badge shown inline next to PicDefense match count.
* Settings page: PicDefense User ID and API Key fields with credential status indicator.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.4.0 =

_Release date: 2026-05-05_

**Added**

* Full attachment detail page accessible via "Full Report" row action in the audit list and "View full report →" link in the quick-view modal. Shows file metadata, internal usage, and complete raw external scan results per provider:
  * Google Vision: pages with matching images (URL + page title), full image matches, and partial image matches — all as clickable links.
  * TinEye: backlinks table with webpage URL, direct image URL, and crawl date — sorted by crawl date, newest first.

**Fixed**

* `rate_limit_per_minute` setting (Settings page) was stored but never read by providers — both Google Vision and TinEye used a hardcoded limit of 10 req/min regardless of configuration.
* `external_batch_size` setting was ignored by Action Scheduler batch processing and by the browser AJAX runner — both always processed 10 attachments per batch.
* TinEye domain extraction used the wrong API field: `backlink.url` is the direct image URL (CDN), while `backlink.backlink` is the webpage URL. The `top_domains` summary and the detail page now show the correct page domains and URLs.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.3.0 =

_Release date: 2026-05-05_

**Added**

* Per-provider scan tracking table (`mra_provider_status`) with composite primary key `(attachment_id, provider)`. Each API provider now has an independent row tracking its scan status, so adding a new provider only queues the missing scans rather than resetting everything.
* "Sync providers" operation in the Tools page: inserts rows for any provider that has no existing entry for a given attachment, then schedules a background scan batch for the new work only.

**Changed**

* `mra_external_results.provider` column changed from `ENUM('google_vision','tineye')` to `varchar(100)` to allow third-party providers registered via the `mra/external/providers` filter to store results.
* External scan batch processing now reads from `mra_provider_status` and processes each provider independently. Aggregate `external_status` on `mra_media_index` is recomputed after each scan and used only for display.
* `ExternalScanner::get_pending_count()` counts queued rows from `mra_provider_status`.
* DB schema version bumped to `1.1.0`. Migration runs automatically on admin_init when updating from an older version; existing data is backfilled into `mra_provider_status`.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.2.0 =

_Release date: 2026-05-02_

**Added**

* Browser-based AJAX scan runner in the Tools page: run index, usage, and external scan batches directly from the browser with a live progress bar. Useful when WP-Cron is disabled or Action Scheduler is not running.
* "Requeue scan errors" operation: re-queues only attachments that failed the last external scan run (status `error`), without touching already-scanned items.
* "Last Scanned" column in the audit list, showing a human-readable relative time with a precise date tooltip. Sortable with NULLs always listed last.
* Bulk action "Export External Results CSV": downloads a CSV with attachment metadata and all external scan results, including top matching domains per provider.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.1.0 =

_Release date: 2026-05-02_

**Changed**

* Plugin URI updated to `https://git.robotstxt.es/ROBOTSTXT/robotstxt-mediaaudit`.
* Author changed to `ROBOTSTXT` with URI `https://www.robotstxt.es/`.
* Contributors updated to `javiercasares, robotstxt`.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4

= 1.0.0 =

_Release date: 2026-05-01_

**Highlights**

* First stable release.

**Added**

* Internal media usage scanner: tracks featured images, inline content, and post meta references across all post types.
* External reverse image search via Google Cloud Vision (Web Detection) and TinEye Commercial API.
* Cross-provider consensus detection: surfaces domains confirmed by multiple providers.
* WP-CLI commands: `wp mra install`, `wp mra status`, `wp mra scan-internal`, `wp mra scan-external`.
* Admin audit list page with filtering by external scan status and pagination.
* Settings page for API credentials, batch size, and rate limiting.
* GDPR compliance: personal data export and erasure via WordPress privacy tools.
* Spanish (Spain) and Catalan translations.
* Action Scheduler integration for async batch processing with configurable batch size and per-minute rate limit.
* `mra/internal/scanner/post_types`, `mra/internal/scanner/meta_keys`, and `mra/external/providers` extension filters.

**Compatibility**

* WordPress: 6.8 - 7.0
* PHP: 8.2 - 8.4
* WP-CLI: 2.x

**Tests**

* PHP Coding Standards: WPCS 3.x / PHPCS 3.x
* PHPStan: level 9
* PHPCompatibility: PHP 8.2 - 8.4
