refactor(files): split the file registry from the disk
FileStorage took Johncms\Http\Request although it outlives one, returned Models\File|Models\File[] depending on a boolean flag, and File built the path, wrote to the disk and inserted the row at once, reaching for the container from inside a domain class. The row it inserted always said 'local' regardless of the disk it had written to, so a second disk would have made every row point at the wrong one.
FileStore now owns both halves, and the order they run in is deliberate: storing writes the file first and removes it again if the row cannot be inserted; deleting removes the row first and only logs a disk that refuses, because a row pointing at nothing is worse than a file nobody points at. Deleting is idempotent, which is what lets guestbook drop DeleteAttachedFilesService — its loop, its try/catch and its logging are deleteMany().
Modules get StoredFileDTO instead of the model: the CKEditor endpoints build their JSON from it, and the URL comes from the disk rather than from UPLOAD_PATH concatenation. Models\File becomes Johncms\Files\StoredFile, a detail of the package behind FileRepositoryInterface, and forum stops querying the core table through it: AttachUploadedFilesToMessageUseCase asks FileStore::filterIdsInDirectory().
The extension a file is stored under is now reduced to letters and digits, and League\Flysystem\FilesystemException no longer appears in any module.