File Handling¶
Flow Diagram¶
graph LR
subgraph Uploads
A1["Admin: Product file upload\nHasDownloadFile trait"] --> S3["S3 disk\ndownload/{table}/{id}/{uuid}.{ext}"]
A2["Admin: Product image upload\nHasImage trait\nIntervention Image v3"] --> S3_IMG["S3 disk\nimages/product/{id}/{HxW}/{name}"]
A3["Admin: CKC serial code ZIP\nUnzipSerialCodeAction"] --> TMP["storage/app/private/tmp/\nspatie/temporary-directory"]
TMP --> CSV["ChekichaImport\nSJIS → UTF-8\nCkcCode records"]
end
subgraph Downloads
B1["GET /product/download/{product}\nGET /product/stream/{product}"] --> AUTH["ProductPolicy::download()\norder status: SUCCESS/SHIPPED/PROCESSING"]
AUTH -->|"authorised"| PSU["5-min presigned S3 URL\nContent-Disposition: attachment"]
AUTH -->|"denied"| ERR["403"]
end
subgraph Exports
C1["Admin: order export\nOrdersExport"] --> SJIS["Shift_JIS CSV\nmaatwebsite/excel"]
C2["Admin: shipping export\nShippingExport"] --> SJIS
C3["Scheduled: Oricon TSV\nOriconTsvExport"] --> SFTP["SFTP\noricon-sftp disk"]
C4["Scheduled: Billboard\nfixed-width ASCII"] --> FTP["plain FTP\nbillboard-ftp disk"]
end
Purpose¶
Handles five distinct file flows: digital product downloads (S3 presigned), image uploads (S3 + resizing), CKC serial code imports (ZIP), admin CSV exports, and scheduled FTP/SFTP reporting.
Digital Download¶
Path: app/Models/HasDownloadFile.php (used by Product model)
Upload flow:
1. Admin uploads file via product form
2. On model saved event: old S3 file deleted, new file written to download/{table}/{id}/{random_uuid}.{ext} on s3 disk
3. Pending file field updated with S3 key
Download flow:
1. User visits /product/download/{product} or /product/stream/{product}
2. Controller calls $this->authorize('download', $product)
3. ProductPolicy::download():
- Guest → always denied
- Authenticated → must have order for this product in SUCCESS, SHIPPED, or PROCESSING status
4. On authorisation: returns 5-minute presigned S3 URL with Content-Disposition: attachment
Key file: app/Models/HasDownloadFile.php, app/Modules/Product/Policies/ProductPolicy.php
Routes: routes/web/product.php → GET /product/download/{product}, GET /product/stream/{product}
Image Upload¶
Path: app/Traits/HasImage.php, app/Models/Image.php
- Polymorphic:
imageable_type/imageable_id(Product and ProductVariation useHasImage) - Intervention Image v3, GD driver
- Product thumbnails: 600×600, 100×100, 80×80 → S3
images/product/{id}/{HxW}/{name} - ProductVariation thumbnails: 400×400, 100×100
- Stored in
imagestable as polymorphic records
CKC Serial Code Import¶
Path: app/Modules/Chekicha/Actions/UnzipSerialCodeAction.php, app/Modules/Chekicha/Imports/ChekichaImport.php
- Admin uploads ZIP file via admin UI
UnzipSerialCodeAction::unzip()extracts tostorage/app/private/tmp/(spatie/temporary-directory, auto-cleaned)- For non-standard ZIPs: fallback to
shell_exec('unar ' . $path)— uses systemunarbinary - CSV files inside ZIP: SJIS → UTF-8 conversion
- Validated via
ChekichaImport(Maatwebsite Excel) with 6 custom rules (see validation.md) - Valid records →
SerialCodeDTOcollection →CkcCode::create()bulk insert - Temporary directory cleaned up after processing
Security note: shell_exec call in extractZipWithUnar uses UploadedFile::path() — safe with current implementation but path must never be user-controllable.
Admin CSV Exports¶
All exports use Maatwebsite Excel with Shift_JIS encoding.
| Export | Class | Content |
|---|---|---|
| Order list | app/Modules/Order/Exports/OrdersExport.php |
All order fields |
| Shipping export | app/Modules/Order/Exports/ShippingExport.php |
Shipping data + Oricon items + bonus items |
| Product template | app/Modules/Product/Exports/SampleProductExport.php |
Import template |
| Variation template | app/Modules/Product/Exports/SampleVariationExport.php |
Import template |
Product Import¶
Path: app/Modules/Product/Imports/ProductImport.php
Uses custom fgetcsv parser (bypasses PhpSpreadsheet — Shift_JIS compatibility). Parses product fields + Oricon items, validates artist + date formats.
Storage Disks¶
Configured in config/filesystems.php. All disks have throw: false (exceptions swallowed on storage errors).
| Disk | Driver | Purpose |
|---|---|---|
local |
local | Private storage |
public |
local | Publicly accessible |
s3 |
S3 | All product assets (downloads, images) |
oricon-sftp |
SFTP | Oricon chart reporting (SSH key auth) |
billboard-ftp |
FTP | Billboard Japan reporting (plain FTP) |
Failure Paths¶
| Scenario | Behaviour |
|---|---|
| S3 unavailable on upload | throw: false — exception swallowed silently |
| S3 unavailable on download | Presigned URL generation fails — 500 or null URL |
| ZIP extraction failure (unar) | shell_exec returns null — exception thrown up to controller |
| Chekicha CSV validation failure | Admin UI receives validation error list |
| SFTP failure (Oricon) | Throwable caught and swallowed in scheduler job — silent |
| FTP failure (Billboard) | Exception propagates — job fails and retries |
Security Considerations¶
- Digital downloads require verified order — no direct S3 URL exposure to unauthorised users
- Presigned URLs expire in 5 minutes — limit exposure window
shell_execinextractZipWithUnar— safe with current path source; do not pass user-controlled stringsssl_verify_peer = falsenoted in Google Maps service (not related to file handling but same config pattern)- Plain FTP for Billboard transmits credentials in cleartext