=== Mediaspy - Detect & Clean Unused Media ===
Contributors: masum52
Tags: media, cleanup, unused media, media library, disk space
Requires at least: 5.8
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Find unused files in your WordPress media library and safely delete them to free up disk space.

== Description ==

Mediaspy scans your entire WordPress site and shows you which media files are not used anywhere. Review the results, then delete what you don't need - one by one or all at once.

**Why Mediaspy?**

Most plugins check only post content. Mediaspy checks everything - page builders, widgets, theme CSS, customizer settings, code snippets, and more - before calling a file unused.

= What gets scanned =

* Post and page content
* Featured images
* Page builder data (Elementor, Divi, WPBakery, Kadence, and more)
* Theme CSS files
* Widget and Customizer settings
* Code snippet plugins (WPCode, Code Snippets, FluentSnippets)
* Plugin options stored in the database

= Features =

* **Scan your full media library** - every image, video, audio file, PDF, archive, and document
* **See where each file is used** - click any file to see every page, post, or setting referencing it
* **Grid and List view** - browse results as cards or a full table
* **Filter by file type** - Images, Videos, Audio, PDFs, Archives, Documents
* **Search by filename** - find any file instantly
* **Sort by size or date** - tackle the biggest space wasters first
* **Bulk delete** - select multiple files and delete with one click
* **Download ZIP** - back up unused files before deleting
* **Import ZIP** - restore a previously exported archive back into your library
* **Live sync** - uploads and deletions made elsewhere reflect instantly, no re-scan needed
* **Broken media detection** - find content that links to files which no longer exist
* **One-click thumbnail regeneration** - rebuild missing image sizes whose original still exists
* **Ghost attachment cleanup** - remove media library records whose file is gone
* **Foreign media resolver** - import externally-hosted images into your library and rewrite every reference, or remove dead external links (with a Live vs Stored exposure signal)
* **Upload Guard - duplicate detection at upload time** - filesize prematch then md5 of the original bytes catches duplicates as they are uploaded
* **Oversize upload alerts with warn or block mode** - separate size limits for images and other files
* **Missing alt text attention list with inline editing** - flag images without alt text and fix them in place
* **Zero performance impact** - only runs when you click Scan Now, nothing loads on the front end

= Works with =

Gutenberg, Elementor, Divi, WPBakery, Kadence Blocks, GenerateBlocks, Spectra,
Beaver Builder, Brizy, Oxygen, Bricks, Cornerstone, Thrive Architect, SiteOrigin,
Avada/Fusion, ACF, WooCommerce, Slider Revolution, MetaSlider, Envira, Soliloquy,
and any builder that stores data in post meta, options, post content, or its own
tables. Mediaspy matches images by URL (including every generated size), by
filename, and by attachment ID, so builders that reference a resized copy or an
ID-only shortcode are detected too.

== Installation ==

1. Go to **Plugins -> Add New** in your WordPress admin
2. Search for **Mediaspy**
3. Click **Install Now** then **Activate**
4. Go to **Media -> Mediaspy** and click **Scan Now**

= Manual install =

1. Download the ZIP file
2. Go to **Plugins -> Add New -> Upload Plugin**
3. Upload the ZIP and click **Install Now**
4. Activate the plugin
5. Go to **Media -> Mediaspy** and click **Scan Now**

== Frequently Asked Questions ==

= Is it safe to delete files? =

Mediaspy checks every file against your full database before marking it unused. Even so, always take a site backup before bulk-deleting. Use the **Download ZIP** button to save a copy of unused files before you delete them.

= What if a file shows unused but I can see it on my site? =

Some plugins store data in custom database tables that Mediaspy does not scan (for example, certain booking or events plugins). If you can see a file on your site, do not delete it. Use the search bar to find and skip it.

= Does it work with Elementor, Divi, or other page builders? =

Yes. Mediaspy reads the raw post meta where builders store their data, and also scans global plugin settings in the database.

= Does it detect images used in the Site Editor or as CSS backgrounds? =

Yes. The scanner reads Global Styles and handles JSON-escaped URLs, so background images set through the Site Editor are correctly detected.

= Will it slow down my site? =

No. The plugin is admin-only. Nothing runs on public-facing pages. The scan only starts when you click **Scan Now**.

= Can I get a deleted file back? =

No - deletion is permanent. Use **Download ZIP** to save a backup before deleting if you are unsure.

= Does it support Multisite? =

Not in this version. Mediaspy is designed for single-site installations.

= What is a broken media reference? =

A link in your content, page-builder data, options or CSS that points to a
media file which no longer exists on disk. Mediaspy classifies each as
deleted, missing size, ghost attachment, or foreign domain.

= What does "missing size" mean? =

WordPress generates resized copies (e.g. image-300x200.jpg). "Missing size"
means a resized copy is gone but the original is still present - Mediaspy can
regenerate the missing sizes for you with one click.

= How long does a scan take? =

Under 5 seconds for most sites. Libraries with 5,000+ files may take up to 30 seconds.

= Will Upload Guard block my imports? =

No. Automated uploads (cron, WP-CLI, and importers/sideloads) are never
blocked - they are only logged. Blocking applies only to interactive uploads
in wp-admin, and only when a rule is set to "Block". To compare against files
uploaded before Upload Guard existed, run the one-time library hashing from the
Guard settings screen.

== Privacy and external services ==

Mediaspy runs entirely on your own site and does not phone home during normal use.

The only outbound request is optional deactivation feedback: if you click
"Submit & Deactivate" in the feedback box shown when deactivating the plugin,
your selected reason, your optional message, your (editable) admin email, your
site URL, and your plugin/WordPress/PHP versions are sent to the developer's
feedback endpoint. Clicking "Skip & Deactivate" sends nothing. No data is sent
unless you explicitly submit the form.

== Screenshots ==

1. Scan results - stat cards showing total, used, unused, and wasted space
2. Grid view - file cards with quick Delete and View actions on hover
3. List view - full table with file type, size, date, and actions
4. View Details modal - every post, page, and setting that references a file
5. Search and sort controls

== Changelog ==

= 1.3.0 =
Accuracy overhaul for page builders, plus new builder coverage and a deactivation feedback box.

New:
* Slider Revolution and MetaSlider support - images used only in Slider Revolution's own tables, or linked to a MetaSlider slideshow, now count as used instead of being flagged unused
* Brizy support - base64-encoded Brizy builder data is decoded so its images are detected
* "View Details" now finds the same size-variant and ID references as the scan, so a used file is never shown as referenced nowhere

Fixed:
* Images used at a specific size are no longer misreported as unused. The scanner now matches every generated size (e.g. image-768x512.jpg), not just the original file - this resolves false "unused" results with Oxygen's "choose a size" workflow and any page builder that stores a resized URL
* Builders that reference an image by ID inside a shortcode are now detected - WPBakery (image="ID", images="1,2,3"), Divi galleries (gallery_ids) and Avada/Fusion (image_id)
* Oxygen 4 (ct_builder_json) double slash-escaped URLs are now matched

Compatibility:
* Tested up to WordPress 7.1

Internal:
* Detection rules moved into a dedicated, unit-tested UsageDetector shared by the scan and the usage lookup

= 1.2.0 =
* New: Upload Guard - duplicate detection at upload time (filesize prematch + md5 of original bytes, so scaled/rotated images are matched correctly)
* New: Upload Guard oversize alerts with off / warn / block modes and separate image vs other-file size limits
* New: Upload Guard missing-alt attention list with inline editing
* New: resumable library hashing backfill so duplicate detection covers files uploaded before the guard existed; the same pass flags existing images missing alt text
* Note: programmatic uploads (cron, WP-CLI, sideloads/importers) are logged, never blocked
* New: Foreign Media Resolver - foreign_domain references are now actionable
* New: Localize - download an externally-hosted image into your Media Library and rewrite every reference (post content, custom fields, options, term meta) to the new local URL, serialization-safe
* New: Remove reference - safely clear a dead external link from its stored data
* New: exposure signal (Live vs Stored) so you can tell a real front-end dependency from leftover orphan data
* New: extra_local_domains setting to treat a CDN / migrated domain as local
* Security: SSRF-guarded downloads (private/loopback/link-local IPs blocked, image MIME sniff, size cap, SVG opt-in)

= 1.1.0 =
* New: Broken Media Detector - finds references to files that no longer exist
* New: classifies broken references (deleted, missing size, ghost attachment, foreign domain)
* New: one-click thumbnail regeneration for missing image sizes
* New: ghost attachment cleanup for media records whose file is gone
* New: optional extra local domains setting for CDN / migrated URLs
* Internal: Scanner corpus refactored into a single shared indexed pass (no change to unused-scan results)

= 1.0.1 =
* Refactor: rebuilt on a modular MVC structure with PSR-4 services and Composer autoloading
* New admin interface built with Vue 3 and bundled with Vite
* Scan, delete, usage details, export and import now run over the REST API (replacing admin-ajax)
* Security: hardened the ZIP import - reject path-traversal entries (Zip-Slip), extract outside the web root, and cap archive size and file count against decompression bombs
* Security: removed an over-eager import de-duplication step that could delete unrelated media sharing the same filename
* Added uninstall cleanup (removes the cached scan, transients and temporary export/import folders)
* Refreshed the header branding with the Mediaspy logo
* Source files normalised to ASCII (fixes garbled comment characters)
* No change to detection accuracy or everyday workflow

= 1.0.0 =
* Initial release
* Full media library scan with 6-layer usage detection
* Grid and list view with search, filters, and sorting
* Bulk delete, Download ZIP, and Import ZIP
* View Details modal showing every usage location per file
* Live two-way sync with the WordPress Media Library

== Upgrade Notice ==

= 1.3.0 =
Major accuracy fix. Images used through Oxygen, WPBakery, Brizy, Slider Revolution, MetaSlider, or at any specific/resized size are no longer falsely reported as unused. Strongly recommended.

= 1.2.0 =
Adds Upload Guard (upload-time duplicate detection, oversize alerts, missing-alt attention list) and the Foreign Media Resolver. Recommended update.

= 1.1.0 =
Adds the Broken Media Detector. Recommended update.

= 1.0.1 =
Recommended update. Internal refactor (MVC + Vue + REST) plus security hardening of the ZIP import. Same features and scan accuracy as 1.0.0.

= 1.0.0 =
Initial release.
