Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /guides/Storage.md for this page in Markdown format</div>
Storage Refactoring and S3 Integration
API
Methods
Storage::get($path)Storage::put()Storage::getStream($path)Storage::putStream($path, $stream, $options = [])Storage::listContents($directory = '')Storage::move($from, $to)Storage::delete()- Path normalization in all methods
Helper
storage()with default typefilesand default disklocalif not set in .env
Storage Structure
The folders below (except user_langs) were moved from public/files. user_langs is the generated language files which were originally in storage root folder. All moved folders in storage shouldn't be public. They should be moved before running MoveLocalToS3 Command
storage/
├── forms/ # Moved from public/files/forms
├── imports/ # Temporary storage for imports
│ └── temp/ # Temporary unzipped files
├── invoices/ # Moved from public/files/invoices
├── user_langs/ # User language files
└── vouchers/ # Voucher files
└── dhl/ # DHL-specific vouchersConfiguration - Environment Variables
Configuration for new types and disks is in application/config/storage.php
Currently, types files and iqvia are already supported. When not set in .env the default values are:
#FILES_STORAGE_DRIVER=local
#IQVIA_STORAGE_DISK=sftpLocal Storage
For local storage and type files there is nothing to setup FILES_STORAGE_DISK can be set to local or empty which is the default in application/config/storage.php
#FILES_STORAGE_DISK=localSFTP Storage
#<STORAGE_TYPE>_SFTP_HOST=
#<STORAGE_TYPE>_SFTP_USERNAME=
#<STORAGE_TYPE>_SFTP_PASSWORD=
#<STORAGE_TYPE>_SFTP_PORT=22
#<STORAGE_TYPE>_SFTP_ROOT=/
#<STORAGE_TYPE>_SFTP_TIMEOUT=30
#<STORAGE_TYPE>_SFTP_PRIVATE_KEY=null
#<STORAGE_TYPE>_SFTP_PASSPHRASE=nullS3 Storage
#<STORAGE_TYPE>_S3_ACCESS_KEY_ID=
#<STORAGE_TYPE>_S3_SECRET_ACCESS_KEY=
#<STORAGE_TYPE>_S3_DEFAULT_REGION=
#<STORAGE_TYPE>_S3_BUCKET=
#<STORAGE_TYPE>_S3_ENDPOINT=
#<STORAGE_TYPE>_S3_AWS_USE_PATH_STYLE_ENDPOINT=
#<STORAGE_TYPE>_S3_URL=
#<STORAGE_TYPE>_S3_PREFIX=AdvUploader Updated
Storage is hooked on AdvUploader class. The validation has been extracted from codeigniter to standalone class The persistence of files is done through storage helper
CI_Upload can be used for not public files, although a local driver for the specific folder could be created
Migration Process
Prepare code updates for migration
This will be handled after merging from master to client branch.
If there are folders in public/files that should not be public update UpgradeStorage like this
php
public function execute()
{
parent::execute();
//and add commands for moving additional folders
(new InternalMoveFolder())->execute('files/the-folder', '../storage/the-folder');
}and create the-folder in /storage
Directory guard stubs are preserved automatically
InternalMoveFolder never moves directory scaffolding out of the web root — the 403 guard index.html that ships in each public/files/* folder, plus .gitignore and .gitkeep. A folder pair you add above gets that behaviour for free; you do not need to protect your own stub.
The 403 guard is recognised by content, not by name: an index.html of at most 4096 bytes containing the string 403 Forbidden. A genuine index.html upload is therefore still moved normally, at any nesting depth. execute() also remediates the older, unfiltered behaviour: a recognised guard already sitting at the destination is moved back to the source, or deleted when the source already has one. The destination sweep considers index.html only — it never touches the tracked .gitkeep that every /storage folder ships.
To add your own filenames to the skip list, redeclare the constant in application/libraries/StorageJobs/InternalMoveFolder.php using the spread form:
php
class InternalMoveFolder extends AdvInternalMoveFolder
{
protected const GUARD_FILES = [...parent::GUARD_FILES, 'client-marker.txt'];
}Do not use
array_merge()here.protected const GUARD_FILES = array_merge(parent::GUARD_FILES, ['client-marker.txt']);is a compile-time fatal —Constant expression contains invalid operations— which takes the whole file down, not just the job. The spread form is the only composition that compiles.
Two more rules ride with the constant: the base class reads it as static::GUARD_FILES, so a subclass redeclaration really does win (never change that to self::, which silently returns the parent list); and the constant must stay protected or wider and must not be marked final — narrowing it to private in a subclass is a fatal, and final closes the seam.
If your stub is worded differently and does not contain 403 Forbidden, override protected function isDirectoryIndexGuard(string $path): bool instead of adding index.html to GUARD_FILES — adding the name there would also stop genuine index.html uploads from being moved.
Execute Migration Process
- Backup existing files
- Verify storage configuration
- Migration executes
UpgradeStoragewhich moves folders that shouldn't be public in/storagefolder
Move public files to S3
- run
php cli.php job/CopyLocalToS3 - when ready setup nginx or apache (whichever is front) as reverse proxy for
/filesfolder as described in docs/servers/ApacheNginxReverseProxy - set env var
FILES_STORAGE_DISK=s3 - set up mediastream config
application/config/mediastream/services.yamlto have for keyapp.imageFetch.job.resolveFromStorageImageFetchJob:the followingapp.imageFetch.job.resolveFromStorageImageFetchJob: class: EcommerceN\MediaStream\Domain\ImageFetch\Job\Implementation\ProxyCurlFromWebImageFetchJob arguments: $options: proxyTemplatePath: '%env(FILES_S3_URL)%/files/{type}/{fileName}' fetchedTemplatePath: "{locationRoot}/{typeSubDir}/{fileName}" fetchedStorageLocation: ../storage/tmpfiles headers: CURLOPT_RETURNTRANSFER: 1 - ensure images are being served from s3 bucket and run
php cli.php job/DeleteLocalFiles
Getting Demo Files
Clients that need files from demo.ecommercen.com for a new site can run php cli.php job/CopyDemoFiles (@TODO) which will copy demo files to s3 bucket for client