Skip to content

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.phpGET /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 use HasImage)
  • 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 images table as polymorphic records

CKC Serial Code Import

Path: app/Modules/Chekicha/Actions/UnzipSerialCodeAction.php, app/Modules/Chekicha/Imports/ChekichaImport.php

  1. Admin uploads ZIP file via admin UI
  2. UnzipSerialCodeAction::unzip() extracts to storage/app/private/tmp/ (spatie/temporary-directory, auto-cleaned)
  3. For non-standard ZIPs: fallback to shell_exec('unar ' . $path) — uses system unar binary
  4. CSV files inside ZIP: SJIS → UTF-8 conversion
  5. Validated via ChekichaImport (Maatwebsite Excel) with 6 custom rules (see validation.md)
  6. Valid records → SerialCodeDTO collection → CkcCode::create() bulk insert
  7. 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_exec in extractZipWithUnar — safe with current path source; do not pass user-controlled strings
  • ssl_verify_peer = false noted in Google Maps service (not related to file handling but same config pattern)
  • Plain FTP for Billboard transmits credentials in cleartext