Additional requirements :
- SSH / command-line access. The script refuses to run over HTTP.
- PHP 8.0+ (whatever your XenForo installation already requires).
- The file_key column on xf_attachment_data. The script verifies this at startup and
aborts if it is missing rather than guessing at paths.
- Roughly 100 MB RAM per 50,000 attachments when using --index.
- Works with local storage and with remote/off-host Flysystem adapters (S3, R2, etc).
- No add-on installation. Standalone script placed in the forum root.
Attachment Integrity Check
When an attachment's file goes missing but its database row survives, XenForo still renders the attachment link. Clicking it throws a 500 and fills your error log with:
There is no built-in way to find these in bulk, and no way to tell a genuinely lost attachment from one whose file is still sitting in storage under a stale key.
This happens after a database restore, a storage migration, a failed S3/R2 sync, a partially restored backup, or just years of accumulated failed deletes.
What it does
Every attachment is classified as one of three things:
Three modes
Drop the script in your forum root, next to index.php. Then:
That is read-only and changes nothing. --index does a single recursive listing of your storage and answers every lookup from memory — on remote storage that turns ~200,000 network round-trips into ~200 list calls.
Review the CSV. Then back up:
Repair without deleting anything:
Repair and purge the dead rows:
Safety
This tool can delete database rows, so it is built on the assumption that it might be wrong.
posts and threads are not modified. An
Important compatibility note
XenForo derives the stored path from
The script checks for the column at startup and aborts if absent rather than guessing.
Testing
Validated against a live forum with 201,465 attachments on XenForo 2.3 with remote off-host storage, plus a fixture suite covering relink, id-reuse rejection, attach_count maintenance, orphan detection, idempotency, and every guard listed above. A full 200k-row scan takes under two minutes.
Released under BSD 2-Clause. Fork it, extend it, ship it in your own tooling.
Télécharger V1.0.0 :
- SSH / command-line access. The script refuses to run over HTTP.
- PHP 8.0+ (whatever your XenForo installation already requires).
- The file_key column on xf_attachment_data. The script verifies this at startup and
aborts if it is missing rather than guessing at paths.
- Roughly 100 MB RAM per 50,000 attachments when using --index.
- Works with local storage and with remote/off-host Flysystem adapters (S3, R2, etc).
- No add-on installation. Standalone script placed in the forum root.
Attachment Integrity Check
When an attachment's file goes missing but its database row survives, XenForo still renders the attachment link. Clicking it throws a 500 and fills your error log with:
Code:
League\Flysystem\FileNotFoundException: File not found at path: attachments/236/236888-0dcb....data
src/XF/Pub/View/Attachment/View.php:27
There is no built-in way to find these in bulk, and no way to tell a genuinely lost attachment from one whose file is still sitting in storage under a stale key.
This happens after a database restore, a storage migration, a failed S3/R2 sync, a partially restored backup, or just years of accumulated failed deletes.
What it does
Every attachment is classified as one of three things:
- OK — the file is present exactly where XenForo expects it.
- RELINK — the file is missing from its expected path, but a file for this data_id exists under a different file_key and its md5 matches the stored file_hash. The upload is intact; only the database pointer is stale. Repairable with no data loss.
- MISSING — no file at all, or a candidate exists whose md5 does not match, meaning the id was reused by a later upload and that file belongs to somebody else. Nothing recoverable.
Three modes
- Scan — walks the database asking "where is the file?" and writes a CSV. Read-only.
- Orphans — the inverse. Walks storage asking "which row owns this file?", finding files stranded by a database rollback that a normal scan cannot see, because those rows no longer exist. Read-only.
- Apply — consumes the scan CSV, re-verifies every row against storage again, then repairs. Deletes only if you explicitly ask.
Drop the script in your forum root, next to index.php. Then:
Code:
php xf-missing-attachments.php --index --report=scan.csv
That is read-only and changes nothing. --index does a single recursive listing of your storage and answers every lookup from memory — on remote storage that turns ~200,000 network round-trips into ~200 list calls.
Review the CSV. Then back up:
Code:
mysqldump YOUR_DB xf_attachment xf_attachment_data > attach-backup.sql
Repair without deleting anything:
Code:
php xf-missing-attachments.php --apply=scan.csv --index
Repair and purge the dead rows:
Code:
php xf-missing-attachments.php --apply=scan.csv --index --delete
Safety
This tool can delete database rows, so it is built on the assumption that it might be wrong.
- Scanning never writes. Deleting takes a separate run, a separate flag, and a CSV you have had the chance to read.
- Every row is re-verified against live storage immediately before it is touched. A stale or hand-edited CSV cannot cause damage.
- Empty index aborts. If storage is unreachable or the prefix is wrong, every row would look MISSING. That is fatal and --force does not override it.
- Ratio guard. A plan that would delete more than 20% of all attachments is refused — that pattern means a storage fault, not data loss.
- Count guard. --max-delete (default 2000) blocks unexpectedly large plans.
- Deletes go through the entity layer, so attach_count is maintained by XenForo's own attachment handler for every content type, including those from other add-ons.
- Unknown arguments are rejected rather than silently ignored.
- Reports cannot be written to a web-accessible path — they list every attachment on your forum. They go to internal_data/attachment-audit/.
- Refuses to run over HTTP.
posts and threads are not modified. An
[ATTACH] tag whose attachment no longer exists renders as nothing, so the post keeps its text and simply loses a broken image. No thread is ever deleted. Orphaned storage files are reported but never removed — deleting those would destroy the only remaining copy.Important compatibility note
XenForo derives the stored path from
xf_attachment_data.file_key, not file_hash. Both are 32-character hex strings, so confusing the two is easy — and the consequence is that every attachment gets classified MISSING. If you are writing your own tooling for this, that is the trap.The script checks for the column at startup and aborts if absent rather than guessing.
Testing
Validated against a live forum with 201,465 attachments on XenForo 2.3 with remote off-host storage, plus a fixture suite covering relink, id-reuse rejection, attach_count maintenance, orphan detection, idempotency, and every guard listed above. A full 200k-row scan takes under two minutes.
Released under BSD 2-Clause. Fork it, extend it, ship it in your own tooling.
Télécharger V1.0.0 :
Pour consulter le contenu, vous devez : Se connecter ou S'inscrire.
