{"id":50940289,"url":"https://github.com/troychaplin/attached-media-audit","last_synced_at":"2026-06-17T13:31:27.310Z","repository":{"id":365149198,"uuid":"1268390447","full_name":"troychaplin/attached-media-audit","owner":"troychaplin","description":"A plugin that audits media usage on a WordPress site","archived":false,"fork":false,"pushed_at":"2026-06-16T04:22:47.000Z","size":2436,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T05:24:23.369Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/troychaplin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T13:27:57.000Z","updated_at":"2026-06-16T04:22:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/troychaplin/attached-media-audit","commit_stats":null,"previous_names":["troychaplin/attached-media-audit"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/troychaplin/attached-media-audit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troychaplin%2Fattached-media-audit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troychaplin%2Fattached-media-audit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troychaplin%2Fattached-media-audit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troychaplin%2Fattached-media-audit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/troychaplin","download_url":"https://codeload.github.com/troychaplin/attached-media-audit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/troychaplin%2Fattached-media-audit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34451334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T13:31:26.760Z","updated_at":"2026-06-17T13:31:27.298Z","avatar_url":"https://github.com/troychaplin.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"assets/banner-772x250.png\" alt=\"Attached Media Audit\" style=\"width: 100%; height: auto;\"\u003e\n\n# Attached: Media Audit\n\nA WordPress plugin that audits your media library — showing which files are used, where they appear, and which are safe to clean up.\n\n## Features\n\n- **Background scanner** indexes every post's media references via WP-Cron, with live progress feedback\n- **React admin UI** built with `@wordpress/dataviews` — sortable columns, filters, pagination, and bulk actions\n- Detects references in **Gutenberg blocks**, **classic editor** HTML, **featured images**, and **post meta**\n- Flags images with **missing alt text** in content (separate from the Media Library alt field)\n- **Used In** popover shows every post referencing an attachment with a direct edit link\n- Row actions: **Edit**, **View**, **Download**, **Delete Permanently**\n- **Clear Index** resets scan data; **Scan Now** triggers a fresh full scan\n\n## Requirements\n\n- WordPress 6.6+\n- PHP 8.0+\n- Node.js + pnpm (for development builds)\n\n## Installation\n\n1. Copy the `attached-media-audit` folder into `wp-content/plugins/`.\n2. Run `pnpm install \u0026\u0026 pnpm build` from the plugin root.\n3. Activate the plugin in **Plugins → Installed Plugins**.\n4. Navigate to **Media → Media Audit**.\n5. Click **Scan Now** to index your media library.\n\n## Admin UI\n\nFound under **Media → Media Audit**. The table shows all attachments with the following columns:\n\n| Column | Notes |\n|---|---|\n| Preview | Thumbnail or file-type icon |\n| File Name | Links to edit; hover reveals row actions |\n| Type | Image / Video / Audio / Document — filterable |\n| Location | Block / Classic Editor / Featured Image / Post Meta — filterable |\n| Usage | Used / Unused — filterable |\n| Used In | Count of referencing posts; click to open a popover list |\n| Size | File size in human-readable format — sortable |\n| Alt Text | \"No alt\" badge when an image is embedded without alt text |\n| Date | Upload date — sortable |\n\n### Scan states\n\n- **Scan required** — the index has not been built yet (or was cleared)\n- **Unused** — the index is built and this file has no detected references\n- **N posts** — the file is referenced by N posts; click to see them\n\n## Architecture\n\n### Scanner\n\nThe scanner runs as a WP-Cron job in batches of 50 posts. It indexes four reference types:\n\n| Type | Source |\n|---|---|\n| `block` | Gutenberg block attributes (`core/image`, `core/cover`, `core/gallery`, etc.) |\n| `classic` | `\u003cimg\u003e` and `\u003ca\u003e` tags in classic editor HTML |\n| `featured_image` | `_thumbnail_id` post meta |\n| `postmeta` | Other meta keys returning attachment IDs (configurable via `media_audit_scanned_meta_keys` filter) |\n\nAlt text detection for block images reads the rendered `\u003cimg alt\u003e` in `innerHTML` via `WP_HTML_Tag_Processor`, not the block's JSON attributes (which don't store alt for `core/image`).\n\n### REST API\n\n```\nGET /wp-json/attached-media-audit/v1/media\n```\n\nParameters: `page`, `per_page`, `search`, `orderby` (`title|date|usage|file_size`), `order` (`asc|desc`), `type_filter`, `ref_filter`, `usage_filter` (`used|unused`).\n\nReturns server-paginated results with `X-WP-Total` and `X-WP-TotalPages` headers.\n\n### Database\n\nTable: `{prefix}media_audit_index`\n\n| Column | Type | Description |\n|---|---|---|\n| `id` | `bigint` | Auto-increment primary key |\n| `attachment_id` | `bigint` | Foreign key to `wp_posts.ID` |\n| `source_post_id` | `bigint` | Post where the reference was found |\n| `reference_type` | `varchar(20)` | `block`, `classic`, `featured_image`, or `postmeta` |\n| `missing_alt` | `tinyint(1)` | 1 when the image is used in content without alt text |\n| `last_scanned` | `datetime` | When this row was last written |\n\nFile sizes are cached in post meta (`_Attached_Media_Audit_filesize`) on the first scan to support fast SQL `ORDER BY`.\n\n### DB versioning\n\nThe DB version is tracked in the `Attached_Media_Audit_db_version` option. Bumping `ATTACHED_MEDIA_AUDIT_VERSION` in `attached.php` triggers `dbDelta()` automatically on the next page load via `Plugin::maybe_upgrade_db()`.\n\n## Development\n\n```bash\npnpm install\npnpm build      # production build\npnpm start      # watch mode\n```\n\n**Entry:** `src/media-audit/index.js`  \n**Output:** `build/media-audit-admin.{js,css,asset.php}`  \n**Webpack:** extends `@wordpress/scripts` defaults via `webpack.config.js`\n\n### Key source files\n\n```\nincludes/\n  db/class-index-table.php          — schema, get_attachments_rest(), replace_for_post()\n  rest/class-media-controller.php   — REST endpoint, prepare_item()\n  scanner/class-block-parser.php    — Gutenberg block attachment extraction\n  scanner/class-classic-parser.php  — Classic editor HTML parsing\n  scanner/class-meta-parser.php     — Post meta scanning\n  scanner/class-post-scanner.php    — Orchestrates parsers, writes to index\n  scanner/class-batch-runner.php    — WP-Cron batching, progress tracking\n  admin/class-ajax-handler.php      — AJAX actions (scan, progress, locations, clear)\n  admin/class-admin-menu.php        — Asset enqueue, wpMediaAudit JS global\n  class-plugin.php                  — Bootstrap, DB upgrade check, hooks\n\nsrc/media-audit/\n  App.js                            — DataViews component, field definitions\n  hooks/useMediaAudit.js            — REST fetch with AbortController\n  hooks/useScanProgress.js          — AJAX polling, scan state machine\n  components/ScanToolbar.js         — Scan Now + Clear Index + progress bar\n  components/ThumbnailCell.js       — Image preview or dashicon fallback\n  components/TitleCell.js           — Filename + hover row actions\n  components/UsedInCell.js          — Toggleable Popover listing source posts\n  styles.scss\n```\n\n## Hooks\n\n**`media_audit_scanned_meta_keys`** — Filter the list of post meta keys the scanner checks for attachment IDs.\n\n```php\nadd_filter( 'media_audit_scanned_meta_keys', function( array $keys ): array {\n    $keys[] = 'my_custom_image_field';\n    return $keys;\n} );\n```\n\n## License\n\nGPL-2.0-or-later — see [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftroychaplin%2Fattached-media-audit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftroychaplin%2Fattached-media-audit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftroychaplin%2Fattached-media-audit/lists"}