mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
docs: add file-uploads.md — accepted types, limits, storage, ordering, security
This commit is contained in:
1
TODO.md
1
TODO.md
@@ -15,6 +15,7 @@
|
|||||||
- [x] **form.css** — styles for `.tfe-file-queue`, `.fq-item`, `.admin-file-list-item` (sortable), drag handles, label inputs, ghost class
|
- [x] **form.css** — styles for `.tfe-file-queue`, `.fq-item`, `.admin-file-list-item` (sortable), drag handles, label inputs, ghost class
|
||||||
- [x] **PHP upload limits** — `.htaccess` + `.user.ini` in `public/` with `upload_max_filesize=512M` / `post_max_size=520M`
|
- [x] **PHP upload limits** — `.htaccess` + `.user.ini` in `public/` with `upload_max_filesize=512M` / `post_max_size=520M`
|
||||||
- [x] **add.php / edit.php / partage/index.php** — use `sortable.min.js` + `file-upload-queue.js` instead of `file-preview.js`
|
- [x] **add.php / edit.php / partage/index.php** — use `sortable.min.js` + `file-upload-queue.js` instead of `file-preview.js`
|
||||||
|
- [x] **docs/file-uploads.md** — reference document covering accepted types, size limits, storage layout, ordering, labels, security, and source file index
|
||||||
|
|
||||||
## Previously completed
|
## Previously completed
|
||||||
- [x] Multi-file upload for thesis files (basic)
|
- [x] Multi-file upload for thesis files (basic)
|
||||||
|
|||||||
223
docs/file-uploads.md
Normal file
223
docs/file-uploads.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
# File Uploads
|
||||||
|
|
||||||
|
Reference for all file upload handling in XAMXAM: accepted types, size limits, storage layout, display behaviour, and ordering.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Upload surfaces
|
||||||
|
|
||||||
|
There are three forms where files can be uploaded:
|
||||||
|
|
||||||
|
| Surface | Path | Who uses it |
|
||||||
|
|---------|------|-------------|
|
||||||
|
| Admin — add thesis | `/admin/add.php` | Administrator |
|
||||||
|
| Admin — edit thesis | `/admin/edit.php?id=N` | Administrator |
|
||||||
|
| Student submission | `/partage/<slug>` | Student via share link |
|
||||||
|
|
||||||
|
All three surfaces share the same backend controller logic (`ThesisCreateController` / `ThesisEditController`) and the same validation rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File categories
|
||||||
|
|
||||||
|
Each uploaded file is assigned a `file_type` that controls how it is displayed on the public TFE page.
|
||||||
|
|
||||||
|
| `file_type` | How displayed | Trigger |
|
||||||
|
|-------------|---------------|---------|
|
||||||
|
| `main` | Inline `<iframe>` with download fallback | `.pdf` extension |
|
||||||
|
| `image` | `<img>` | image/* MIME or image extension |
|
||||||
|
| `video` | `<video controls>` with Range support | video/* MIME or video extension |
|
||||||
|
| `audio` | `<audio controls>` with Range support | audio/* MIME or audio extension |
|
||||||
|
| `caption` | Not displayed — paired with preceding video | `.vtt` extension |
|
||||||
|
| `cover` | Cover thumbnail (not shown in file loop) | Separate `couverture` input |
|
||||||
|
| `other` | Download link only, never rendered | Everything else |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accepted file types
|
||||||
|
|
||||||
|
### TFE content files (`files[]` input)
|
||||||
|
|
||||||
|
#### Documents
|
||||||
|
| Extension | MIME type | Display |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `.pdf` | `application/pdf` | Inline iframe |
|
||||||
|
|
||||||
|
#### Images
|
||||||
|
| Extension | MIME type | Display |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `.jpg` / `.jpeg` | `image/jpeg` | `<img>` |
|
||||||
|
| `.png` | `image/png` | `<img>` |
|
||||||
|
| `.gif` | `image/gif` | `<img>` |
|
||||||
|
| `.webp` | `image/webp` | `<img>` |
|
||||||
|
|
||||||
|
#### Video
|
||||||
|
| Extension | MIME type | Display |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `.mp4` | `video/mp4` | `<video>` |
|
||||||
|
| `.webm` | `video/webm` | `<video>` |
|
||||||
|
| `.mov` | `video/quicktime` | `<video>` (served as `video/mp4`) |
|
||||||
|
| `.ogv` | `video/ogg` | `<video>` |
|
||||||
|
|
||||||
|
#### Audio
|
||||||
|
| Extension | MIME type | Display |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `.mp3` | `audio/mpeg` | `<audio>` |
|
||||||
|
| `.ogg` / `.oga` | `audio/ogg` | `<audio>` |
|
||||||
|
| `.wav` | `audio/wav` | `<audio>` |
|
||||||
|
| `.flac` | `audio/flac` | `<audio>` |
|
||||||
|
| `.aac` | `audio/aac` | `<audio>` |
|
||||||
|
| `.m4a` | `audio/mp4` | `<audio>` |
|
||||||
|
|
||||||
|
#### Captions (WebVTT)
|
||||||
|
| Extension | MIME type | Behaviour |
|
||||||
|
|-----------|-----------|-----------|
|
||||||
|
| `.vtt` | `text/vtt` | Silently paired with the preceding `<video>`. The N-th `.vtt` file is attached to the N-th video in display order. Not shown as a standalone item. |
|
||||||
|
|
||||||
|
#### Archives and other downloadable files
|
||||||
|
| Extension | MIME type | Display |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `.zip` | `application/zip` | Download link |
|
||||||
|
| `.tar` | `application/x-tar` | Download link |
|
||||||
|
| `.gz` / `.tgz` | `application/gzip` | Download link |
|
||||||
|
| Any other extension | `application/octet-stream` | Download link |
|
||||||
|
|
||||||
|
Files whose MIME type is `application/octet-stream` are accepted **only if their extension is in the known list above**. Unknown extensions with an unknown MIME type are rejected.
|
||||||
|
|
||||||
|
### Cover image (`couverture` input)
|
||||||
|
|
||||||
|
| Extension | MIME type |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `.jpg` / `.jpeg` | `image/jpeg` |
|
||||||
|
| `.png` | `image/png` |
|
||||||
|
|
||||||
|
Max size: **20 MB**.
|
||||||
|
|
||||||
|
### Banner image (`banner` input)
|
||||||
|
|
||||||
|
| Extension | MIME type |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `.jpg` / `.jpeg` | `image/jpeg` |
|
||||||
|
| `.png` | `image/png` |
|
||||||
|
| `.webp` | `image/webp` |
|
||||||
|
|
||||||
|
Landscape format recommended (4:1 ratio). Max size: **20 MB**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Size limits
|
||||||
|
|
||||||
|
| Limit | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Per-file limit (TFE content files) | **500 MB** |
|
||||||
|
| PHP `upload_max_filesize` | 512 MB |
|
||||||
|
| PHP `post_max_size` | 520 MB |
|
||||||
|
| Cover image | 20 MB |
|
||||||
|
| Banner image | 20 MB |
|
||||||
|
|
||||||
|
The PHP limits are set in `app/public/.htaccess` (Apache) and `app/public/.user.ini` (PHP-FPM / nginx). For environments that require higher limits, edit those two files.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File ordering
|
||||||
|
|
||||||
|
Files are displayed on the public TFE page in `sort_order` sequence (ascending). The `sort_order` column is stored in `thesis_files`.
|
||||||
|
|
||||||
|
### Setting order on upload (add / partage forms)
|
||||||
|
|
||||||
|
The file queue in the upload form is drag-sortable via SortableJS. Drag rows into the desired display order before submitting. The order is submitted as `file_orders[]` hidden fields and stored on insert.
|
||||||
|
|
||||||
|
### Changing order after upload (edit form)
|
||||||
|
|
||||||
|
The existing-files list on the edit form is also drag-sortable. Drag rows into the desired order and save — the new order is submitted as `file_sort_order[]` (an array of file IDs in the desired sequence) and persisted via `Database::reorderThesisFiles()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Per-file labels
|
||||||
|
|
||||||
|
Each TFE content file can have an optional **display label** (a short caption or description). This is shown as a `<figcaption>` beneath the file on the public page.
|
||||||
|
|
||||||
|
- On the upload queue: type in the label field below each filename before submitting.
|
||||||
|
- On the edit form: the label input is shown inline in each file row; edited labels are saved alongside the sort order.
|
||||||
|
|
||||||
|
Labels are stored in `thesis_files.display_label`. If blank, the field falls back to the legacy `description` column.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Storage layout
|
||||||
|
|
||||||
|
Files are stored outside the webroot in `app/storage/`.
|
||||||
|
|
||||||
|
```
|
||||||
|
app/storage/
|
||||||
|
├── covers/
|
||||||
|
│ └── <random-hex>.jpg # cover images
|
||||||
|
├── banners/
|
||||||
|
│ └── <random-hex>.jpg # home-page banners
|
||||||
|
└── theses/
|
||||||
|
└── <year>/
|
||||||
|
└── <YEAR>_<AUTHOR_SLUG>/
|
||||||
|
└── <AUTHOR_SLUG>_<sanitized-filename>.<ext>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Author slug: uppercase ASCII, spaces → underscores, accents stripped (e.g. `EMMA_RENARD`).
|
||||||
|
- Filename: same normalisation applied to the original filename.
|
||||||
|
- If a folder `<YEAR>_<AUTHOR_SLUG>` already exists a numeric suffix is appended (`_1`, `_2`, …).
|
||||||
|
- If a filename already exists in the folder a numeric suffix is appended before the extension.
|
||||||
|
|
||||||
|
Files are never served directly from disk. All access goes through `MediaController` (`/media?path=…`), which enforces:
|
||||||
|
- Path traversal prevention (character whitelist + `realpath()` jail)
|
||||||
|
- Visibility gate: `access_type_id = 3` (Interdit) → HTTP 403
|
||||||
|
- MIME allow-list check before serving
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security notes
|
||||||
|
|
||||||
|
- MIME type is verified via `finfo` (magic bytes), not the browser-supplied `Content-Type`.
|
||||||
|
- Extension is additionally checked against the allow-list as a second gate.
|
||||||
|
- Filenames are sanitised (accents stripped, non-alphanumeric → `_`) before writing to disk; the original name is stored in `thesis_files.file_name` for display.
|
||||||
|
- Cover and banner images are stored under a random 32-hex-char name, completely decoupled from the original filename.
|
||||||
|
- Uploaded files are `chmod 0644` after move.
|
||||||
|
- HTTP Range requests are supported for audio and video so the browser can seek without downloading the entire file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database schema reference
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE thesis_files (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
thesis_id INTEGER NOT NULL,
|
||||||
|
file_type TEXT NOT NULL, -- 'main'|'image'|'video'|'audio'|'caption'|'cover'|'other'
|
||||||
|
file_path TEXT NOT NULL, -- path relative to STORAGE_ROOT
|
||||||
|
file_name TEXT NOT NULL, -- original filename (display only)
|
||||||
|
file_size INTEGER, -- bytes
|
||||||
|
mime_type TEXT,
|
||||||
|
description TEXT, -- legacy caption field
|
||||||
|
display_label TEXT, -- per-file caption (migration 007)
|
||||||
|
sort_order INTEGER NOT NULL DEFAULT 0, -- display order (migration 007)
|
||||||
|
uploaded_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (thesis_id) REFERENCES theses(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Files are queried ordered by `sort_order ASC, uploaded_at ASC`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relevant source files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `app/src/Controllers/ThesisCreateController.php` | Upload validation + storage on create |
|
||||||
|
| `app/src/Controllers/ThesisEditController.php` | Upload validation + storage on edit; reorder + label save |
|
||||||
|
| `app/src/Controllers/MediaController.php` | Secure file serving with Range support |
|
||||||
|
| `app/src/Database.php` | `insertThesisFile`, `reorderThesisFiles`, `updateThesisFileLabel`, `getThesisFiles` |
|
||||||
|
| `app/templates/partials/form/fieldset-files.php` | Upload UI partial (add / partage forms) |
|
||||||
|
| `app/templates/admin/edit.php` | Edit-form files section (sortable existing files + new upload queue) |
|
||||||
|
| `app/templates/public/tfe.php` | Public rendering of all file types |
|
||||||
|
| `app/public/assets/js/file-upload-queue.js` | SortableJS-backed upload queue + legacy preview |
|
||||||
|
| `app/public/.htaccess` | PHP upload limits (Apache) |
|
||||||
|
| `app/public/.user.ini` | PHP upload limits (PHP-FPM / nginx) |
|
||||||
|
| `app/migrations/applied/007_thesis_files_sort_and_label.sql` | DB migration adding `sort_order` + `display_label` |
|
||||||
Reference in New Issue
Block a user