=== SEO'n'AI - Image Locator & Find Unused Images ===
Contributors: gwalter75
Tags: media library, unused images, clean up media, image usage, media trash
Requires at least: 6.0
Tested up to: 7.1
Stable tag: 1.0.13
Requires PHP: 7.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

See where every image is used — and find the ones that are not. Delete unused images safely: live re-check first, and a trash you can restore from.

== Description ==

Every media library grows. Sooner or later you look at an image and ask the question WordPress cannot answer: **is this still used anywhere?**

Core shows you "Uploaded to" — the post an image happened to be attached to, which says nothing about where it actually appears. Image Locator answers the real question:

* A **Usage column** in the media list: used in how many places, unused, or not checked yet — with the first findings in the tooltip.
* A **"Used in" panel** on every image (list view and grid modal): each post, page, widget or setting that references the image, linked for one-click editing.
* A **usage page per image**: the image on one side, every place it is used on the other — each opening in a new tab for editing. Checked live on every visit, with a delete button that warns you properly if the image is in use.
* A **filter**: show only used, unused or unchecked images.
* A **library scan** that works through everything in small time-budgeted batches — it cannot time out, even on large libraries.
* A **bulk action to delete unused images** — see below for why it is the careful kind.
* The **SEO'n'AI Trash for unused Images**, on by default in new installations: what this plugin deletes can be restored — with its original attachment ID — until you empty the trash.

= The safety net comes first =

Finding unused images is the easy half. The half that can hurt is the deleting, and that is where most plugins of this kind leave you alone: WordPress has no trash for media unless a line in `wp-config.php` says so, so a deleted image is simply gone from the server.

Image Locator brings its own — the **SEO'n'AI Trash for unused Images**, switched on from day one. Every image the plugin deletes goes there first: the file stays on the server, the image keeps its attachment ID, and one click puts it back so that every post, page and page-builder element referencing it works again. A file from a backup cannot do that — it returns with a new ID, and the gaps stay.

That turns the honest limit of any scanner into something you can live with. **Delete, then look at your website, then empty the trash.** And if the site does run WordPress' own media trash, the plugin recognises it and uses that one instead.

= Where it looks =

The scan searches the database, not just the post content:

* Post content: image blocks, classic editor markup, galleries — matched by attachment ID *and* by every size variant of the file name
* Featured images
* Custom fields and **page-builder data** (Elementor, Divi, WPBakery and others store image references in post meta as IDs or URLs — both are searched)
* Widgets, the site logo, the site icon
* Term images, including WooCommerce category images

Drafts count as usage — those are exactly the ones you want to know about before deleting. Revisions and trashed items do not count.

= Deleting, done carefully =

"Unused" from a cached scan is not enough to delete a file. The bulk action **"Delete unused (verified)"** re-checks every selected image live, immediately before deletion. If an image turns out to be in use after all — because content changed since the scan — it is skipped and reported, not deleted. With the safety net on, the same action is called **"Move unused to SEO'n'AI Trash (verified)"**: same check, reversible outcome.

And if you try to delete an image the scan knows is used, the media list warns you first, with the number of places it appears.

= The trash in detail =

WordPress has a trash for posts and pages. For media it has one too, but it stays switched off unless `wp-config.php` contains `define( 'MEDIA_TRASH', true );` — and no plugin can switch it on for you, because WordPress settles that constant before plugins are even loaded. That is why the plugin keeps its own instead of promising something it cannot deliver.

**Where it lives:** on **Media → Image Locator** — every deleted image with a preview, the day it was deleted, the days left before WordPress clears it, "Restore", "Delete permanently" and "Empty trash". Without `MEDIA_TRASH`, the media library itself does not list trashed images; that is WordPress, not the plugin, and the reason the trash has its own page here.

**Three ways lead into it**, and they all belong to this plugin: the bulk action **"Move unused to SEO'n'AI Trash (verified)"** in the media list, the **"Move to SEO'n'AI Trash"** link under an image in list view, and the button on an image's usage page. What WordPress itself calls **"Delete permanently"** stays exactly that and removes the file at once — in the list as well as in the grid view, where WordPress offers plugins no place for an extra action. If you want *every* deletion on the site to be caught, add the `wp-config.php` line above; the plugin then steps aside and uses WordPress' own media trash.

**Mind the clock:** WordPress empties the trash by itself after 30 days (`EMPTY_TRASH_DAYS`). Do your check within that window. On a site that sets `EMPTY_TRASH_DAYS` to 0, no trash of any kind is possible — the plugin says so plainly instead of promising a net it cannot hold.

**Installing, updating, removing:** new installations start with the safety net on. A plugin update never changes the behaviour of an existing site; the switch simply sits on the tools page, ready. And if you ever delete the plugin, it puts every image it had trashed back into the media library before it uninstalls itself — nothing is left behind that WordPress would quietly clear out later. Deactivating does not touch anything, but the plugins screen warns you while images are still in the trash.

Without the net, deletion is permanent and removes the actual files, like WordPress' own delete. Keep backups either way.

= Honest about the limits =

A database scan cannot see everything. Images hard-coded in theme or plugin template files, images referenced from custom CSS, and use on other websites are invisible to any plugin of this kind. Treat "unused" as strong evidence, not as a court ruling — that is exactly why the bulk delete re-checks each image, and why nothing is ever deleted automatically.

= Disclaimer =

This plugin is a tool, not a promise. It reads your database and reports what it finds; it cannot see images hard-coded in theme or plugin template files, referenced from custom CSS, or used on other websites. **Deleting images is always your decision and your responsibility**: every deletion is started by you and confirmed by you. The trash buys you time to notice a mistake, but it is not a backup and it does not last forever — once it is emptied, by you or by WordPress after 30 days, the files are gone. Verify the findings before you delete (the usage page exists exactly for that) and keep backups. The author accepts no liability for deleted images, broken layouts or any other consequences of using this plugin.

= Performance =

The media list never scans on render — the column reads only cached results. Scanning happens when you ask for it: per image with one click, or for the whole library in batches with a strict time budget per request. When content changes, existing results are marked as outdated instead of silently lying to you.

One honest trade-off: **a full library scan takes time on large libraries.** Every image is checked individually, with the same logic that runs before any deletion — one truth, one code path. On a library with thousands of images that can take a while; on a typical site it is a few minutes, and it is a one-time cost — after the first full run, "Check new images" only looks at new uploads. To really catch everything, thoroughness beats speed. The front end of your site is never touched either way.

== Installation ==

1. Upload the `seonai-image-locator` folder to `/wp-content/plugins/`, or install it through the plugin installer.
2. Activate the plugin.
3. Go to **Media → Image Locator** and run the library scan.
4. Check the new **Usage** column in the media list (list view), or open any image to see where it is used.
5. On a new installation the safety net is switched on for you — deleted images go to the SEO'n'AI Trash on that same page, where you can restore them. Existing installations keep whatever they had; the switch is there when you want it.

== Frequently Asked Questions ==

= Does it work with page builders? =

Yes, within honest limits. Builders like Elementor, Divi and WPBakery store image references in post meta — as attachment IDs, as file URLs, or both. The scan searches post meta for the ID patterns and for every file name variant of the image. Exotic storage formats a builder might invent can escape any scanner; the live re-check before deletion is the safety net.

= Why does an image show "outdated"? =

Something on the site changed since that image was last checked — any saved post or updated widget triggers it, for all images at once. It is not a statement about this particular image: if you have not placed the image anywhere since, the result still stands, and unused is still unused. Re-check the image or re-run the library scan to make it fresh. Bulk deletion never relies on outdated results anyway — every image is re-checked live right before deletion.

= The library scan takes a long time. =

That is thoroughness, not a hang — the progress counter shows the current run moving. Every image is checked individually against content, custom fields, page-builder data, widgets and term images, using the same logic that runs before any deletion. Large libraries with thousands of images take correspondingly long; to really catch everything, that time is the price. Keep the tab open — the scan works in small batches and cannot time out. It is a one-time cost: after the first full run, "Check new images" only scans new uploads.

= Does it slow down my site? =

No. Everything runs in the admin, and the media list reads only cached values. The visitor-facing site is never touched.

= Why does the plugin keep its own trash? =

Because deleting images is the one step here that cannot be undone, and WordPress ships with the media trash switched off. A plugin cannot switch it on — the constant `MEDIA_TRASH` is settled in `wp-config.php`, before plugins load. So the plugin does the next best thing with WordPress' own mechanism (`wp_trash_post()`): images it deletes are moved to a trash and can be restored with their original ID from **Media → Image Locator**. If your `wp-config.php` does set `MEDIA_TRASH`, the plugin uses WordPress' media trash instead and stays out of the way.

= I deleted images. What should I check before emptying the trash? =

Walk through the places a scan cannot look into: the home page, sliders and hero areas, category and product pages, a few older posts, and any page your builder or theme assembles from its own files. Images hard-coded in template files or referenced only from CSS are invisible to any database scan — if one of them is missing, you will see the gap there. Restore it from the trash and you are done. Only empty the trash once you are sure. Remember that WordPress empties it on its own after 30 days.

= What happens to the trash if I deactivate or delete the plugin? =

**Deleting the plugin is safe:** before it uninstalls itself, it restores every image it put in the trash back into the media library — with the original ID, as always. Nothing of yours is left behind in a place you cannot see.

**Deactivating is different**, and the plugin warns you about it right on the plugins screen while images are still in the trash. Trashed images stay trashed, the media library does not list them (that is WordPress without `MEDIA_TRASH`), and WordPress deletes trashed items for good once `EMPTY_TRASH_DAYS` have passed — with or without this plugin. So restore what you want to keep before you deactivate; one button on the plugins screen or in the trash view does it for all of them at once.

= Does it delete anything automatically? =

Never. Deletion happens only when you select images and run the bulk action yourself — and even then, every image is re-checked live first.

= Is "unused" guaranteed? =

No plugin can guarantee that, and you should distrust any that claims to. Hard-coded template references and external use are invisible to a database scan. That is why deletion is manual, warned, and re-checked.

== Screenshots ==

1. The Usage column in the media list, with the unused filter active
2. The usage page: the image next to every place it is used, each opening in a new tab
3. "Used in" on a single image, with linked findings
4. The library scan on Media → Image Locator
5. The two ways into the trash: the bulk action "Move unused to SEO'n'AI Trash (verified)" and the "Move to SEO'n'AI Trash" link under each image
6. The SEO'n'AI Trash: every deleted image with the days left before WordPress clears it, one click to restore it with its original ID

== Changelog ==

= 1.0.13 =
* New: a safety net for deleting. Images deleted with this plugin are moved to a trash instead of being removed from the server, and can be restored with one click — with their original attachment ID, so everything that referenced them works again.
* New: the SEO'n'AI Trash on Media → Image Locator — every deleted image with a preview, the date it was deleted, the days left before WordPress clears it, "Restore", "Delete permanently" and "Empty trash".
* Reachable from three places, all of them this plugin's own: the bulk action in the media list, a "Move to SEO'n'AI Trash" link under each image, and the button on the usage page. WordPress' own "Delete permanently" is left untouched and still means what it says.
* The plugin recognises when a site runs WordPress' own media trash (MEDIA_TRASH in wp-config.php) and steps back in favour of it, because that one covers every deletion on the site. The tools page states which of the two is in effect, and offers the wp-config line for those who want it.
* Honest about the edge case: on a site where EMPTY_TRASH_DAYS is 0, no trash is possible at all — the plugin says so instead of promising a net it cannot hold.
* After deleting, the plugin says what to check on the site before the trash is emptied: hard-coded template images and CSS backgrounds are invisible to any scan, and that is the moment they would show up as a gap.
* New installations start with the safety net on; an update never changes the behaviour of an existing site.
* Nothing is left stranded: deleting the plugin restores every image it had trashed before it uninstalls itself, and while images are in the trash, the plugins screen warns that deactivating leaves them invisible to the media library — WordPress clears trashed items after EMPTY_TRASH_DAYS either way. "Restore all" is one click, on the plugins screen and in the trash view.

= 1.0.12 =
* Plugin Check: added the missing translators comments for the two delete-confirmation strings, and fixed the placement of a PHPCS annotation so the prepared-SQL check in the options scan is correctly documented. No functional changes.

= 1.0.11 =
* Text domain and plugin folder now match the assigned wordpress.org slug (seonai-image-locator-find-unused-images).

= 1.0.10 =
* Fixed: files that WordPress stores with an image/* mime type but that are not checkable images (SVG, ICO) were counted as "unused" without ever being checked. They are now excluded from the scan, the statistics and the unused count, and leftover results from earlier runs are cleaned up automatically.

= 1.0.9 =
* Code review round: all SQL restructured so that queries with a variable number of IN()/LIKE placeholders are built explicitly and annotated; every value is bound through $wpdb->prepare(). The remaining literal queries (uninstall, statistics) now use prepare() as well.

= 1.0.8 =
* Screenshots reduced to four; the bulk-result screenshot was dropped (the live re-check before deletion is described in the text instead).

= 1.0.7 =
* Documentation: the scan's thoroughness-over-speed trade-off is now stated openly — on the tools page, in the description and as an FAQ. Large libraries take time because every image is checked individually, with the same logic that runs before any deletion; catching everything is the point.

= 1.0.6 =
* Fixed: "Re-check everything" looked stuck — the progress showed the all-time checked count (100% from the start) instead of this run's progress. It now counts the current run.
* Fixed: results were marked "outdated" far too eagerly. Revisions, autosaves and technical post types that never reference images (WooCommerce orders, scheduler actions) no longer trigger the outdated marker — on shop sites, everything used to look permanently outdated.
* Those technical post types are now also excluded from the scan itself.

= 1.0.5 =
* New: a Disclaimer section in the description — the plugin reports, the user decides; deletion is the user's responsibility, and the author accepts no liability for deleted images.
* New: the bulk action now asks for confirmation before running, stating how many images are selected and that the final check is the user's.
* The single-image delete confirmation on the usage page now says so too.

= 1.0.4 =
* Renamed to "SEO'n'AI - Image Locator & Find Unused Images" — the name now says both halves of what it does.

= 1.0.3 =
* New: a usage page per image — the image next to the list of every place it is used, each opening in a new tab for editing. Reached from the usage badge in the media list and from the image panel.
* The usage page always checks live, never the cache, so it never shows "outdated".
* New: a delete button on the usage page, with a confirmation that states the live usage count — and a serious warning if the image is in use.

= 1.0.2 =
* Clearer wording everywhere "outdated" appears: it means something on the site changed since the check — not that this image's result is wrong. If the image was not placed anywhere since, unused is still unused. Explained in the column tooltip, on the image panel and on the tools page.

= 1.0.1 =
* Avada / Fusion Builder: images referenced as `image_id="123|full"` are now detected.
* ID lists are now detected anywhere in the list, not only at its start — this covers Avada galleries and carousels (`image_ids="…"`) and classic `[gallery ids="…"]` shortcodes.

= 1.0.0 =
* Initial release.
