Download the PHP package citomni/upload without Composer

On this page you can find all versions of the php package citomni/upload. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package upload

CitOmni Upload

Validated file intake and local storage for CitOmni applications and packages, with image outputs delegated to citomni/image.

citomni/upload stores files uploaded in the current HTTP request, and local files, under opaque names in configured public and private storages. A named profile decides what is accepted, where it is stored, and which image outputs are produced.

Upload knows when an accepted file needs image processing, which outputs a profile wants, and where they go. It never knows how pixels are processed: that is citomni/image's job.


What this package is

citomni/upload is one transport-agnostic intake engine for HTTP and CLI. storeUpload() and storeLocal() handle one file:

  1. It checks the profile, the arguments, and the storage configuration.
  2. It validates the file: provenance, size, and the content type detected with finfo.
  3. It stores the file under a fresh random name. For an image profile, citomni/image produces the image outputs at paths Upload chose.
  4. It returns a plain array with the storage-relative path and metadata.

The application persists that array. Upload knows nothing about databases, users, or entities. For a multi-file field, storeUploads() runs the same steps for every file and returns the results together with the rejections. An application that stores the bytes through a protocol of its own calls checkUpload(): it validates the file as in step 2 and writes nothing.


What this package owns


What this package does not own


Relationship to other CitOmni packages


Requirements


Installation

Register both providers in the application's config/providers.php:

A Composer dependency is not a service registration. Without the Image provider, neither $this->app->image nor the image.* configuration exists, and every use of a profile with an image block throws UploadConfigException, including calls that store a non-image file, delete files, or derive variant paths. There is no fallback.

The package registers the upload service ($this->app->upload) in HTTP and CLI mode, ships the configuration baseline under upload and the language files for rejection messages, and contributes no routes or commands.


Quick start

Everything in the examples is illustrative. The profile names, controllers, Operations, Repositories, and tables do not exist in CitOmni, and the package ships no profiles.

Define a profile, typically in the application's config/citomni_cfg.php, which HTTP and CLI share:

avatar has no directory. The caller passes the user's opaque upload token and a subdirectory as $subdir, so no internal ID appears in a public path:

The adapter catches only UploadRejectedException; see Usage patterns.


Configuration

The provider contributes this baseline:

Applications override it through the normal CitOmni configuration merge.

Storages

A storage is a root and an optional web_path. storages is an open map: applications override the baseline roots and add storages of their own, for example on a separate mount for large archives.

File and directory modes

Both are validated and memoized on the first store call. An invalid value throws UploadConfigException.

Profiles

A profile is a named upload policy under upload.profiles.<name>. Every method that takes string|array $profile also accepts an inline profile array. Packages that use Upload ship their own profiles, named <package>_<purpose>.

Key Type Default Meaning
storage string required Name of an entry in upload.storages.
directory string '' Base directory below the storage root. The call's $subdir is appended.
accept list of strings required Accepted MIME types, or exactly ['*'].
max_bytes int or null required key Size limit in bytes, at least 1, or null for no limit on Upload's side.
shard int 0 Number of shard directory levels, 0 to 2.
hash string or null null An algorithm from hash_algos(), matched case-insensitively. The result then carries a hash of the main file.
image array or null null Image outputs; see The image block. null stores images as plain files.

The image block

The block decides which outputs exist and which role each has. The specs and options themselves are citomni/image vocabulary and are passed on unchanged: see its output specifications and job options.

Key Rule
main Required key. An output spec means that citomni/image produces the main file, re-encoded and without metadata. null means that Upload stores the original bytes untouched (passthrough).
variants Map of output specs, default []. Keys match [a-z0-9_]+ and must not be main. citomni/image always produces variants from the source.
options citomni/image job options for the whole job, default [], which means the image.* defaults. Must be an array.

Policy ownership

A spec states only what is specific to the job. When quality, compression, or background is omitted, citomni/image applies its image.* defaults. Upload configuration holds no global image defaults:

Validation

A profile is validated when it is resolved: a named profile on first use, after which it is memoized per name, and an inline profile on every call. A profile that fails validation is not memoized. Every violation throws UploadConfigException with a message that starts with the profile's name (Upload profile 'avatar') or with Inline upload profile. That includes an unknown profile, an invalid directory (with the path error as previous), and an image block without the image service.

Profile shapes

Shape Example Behavior
Image with generated outputs avatar (Quick start) citomni/image produces the main file and a variant; metadata is stripped.
Plain files attachment No image block. Images are stored untouched, and citomni/image is never called.
Untouched original with a generated preview archived_document main: null keeps the original, citomni/image produces the preview, and the original is hashed.

Public API

Pure helpers in CitOmni\Upload\Util, without App and without IO:

storeUpload()

Stores a file uploaded in the current HTTP request.

checkUpload()

Validates a file uploaded in the current HTTP request with the intake of storeUpload() and stores nothing. It serves an application that writes the bytes through a storage protocol of its own, for example one that records an intent in its database before any byte is written and keys its storage by that intent. Such an application cannot use Upload's storage, whose names are random and whose files exist before the application knows them, but it still needs Upload's intake policy in front of its own write.

storeUploads()

Stores the files of a multi-file field with one profile. It is optional: it calls storeUpload() for every entry and adds the batch mechanics that nearly every multi-file form needs, while the batch policy stays with the application.

UploadedFiles

UploadedFiles turns a $_FILES entry into flat entries with exactly the keys name, type, tmp_name, error, and size. error and size are ints, and type is the client's claim, which Upload never uses. The class reads no superglobals; the caller passes the entry, as in UploadedFiles::one($_FILES['avatar'] ?? null).

Form field Call Result
avatar one($entry) The entry. many($entry) returns [].
files[] many($entry) A list in client order, without slots that hold no file. one($entry) returns null, and one($entry, 0) the first slot.
files[front] many($entry) or one($entry, 'front') A list without the keys, or that one entry.
doc[a][b], gallery[photos][] one($entry, 'a', 'b'), many($entry, 'photos') The variadic $path selects the nested field.

storeLocal()

Stores a developer-trusted local file, for example in an import command. Content validation is the same as for storeUpload().

archived_document stores in a private storage, where an internal ID in the path is acceptable.

rejectionMessage()

Returns end-user text for a rejection. It takes the exception or its reason. The reason form serves an adapter whose UploadedFiles::one() returned null: rejectionMessage(UploadRejection::NoFile).


Result format

storeUpload() and storeLocal() return the same shape, with the keys in this order, and storeUploads() returns a list of them under stored. The example is an avatar stored with the avatar profile below the user's opaque upload token:

path is relative to the root of the storage that storage names. Persist it; absolutePath() and webPath() derive the rest.

Field Plain file Kept original (main: null) Produced main
path Upload Upload Upload: the path given to citomni/image
mime Detected type Detected type citomni/image result mime
source_mime Detected type Detected type Detected type
size Measured by Upload Measured by Upload; the bytes are unchanged citomni/image result bytes
width, height null inspect() display width and height citomni/image result width and height
hash Of the stored file Of the stored original Of the re-encoded main
variants [] Per variant: Upload's path, and mime, size (bytes), width, and height from citomni/image As for a kept original

Errors and exceptions

The Upload exceptions live in CitOmni\Upload\Exception.

Rejections

The reasons are cases of CitOmni\Upload\Enum\UploadRejection:

Reason Value Raised when context
NoFile no_file UPLOAD_ERR_NO_FILE reaches storeUpload() upload_error
Partial partial UPLOAD_ERR_PARTIAL original_name, upload_error
TooLarge too_large UPLOAD_ERR_INI_SIZE or UPLOAD_ERR_FORM_SIZE original_name, upload_error
TooLarge too_large The size exceeds max_bytes original_name, size, max_bytes
Empty empty The file has 0 bytes original_name, size (0)
TypeNotAllowed type_not_allowed The detected type is not accepted, or finfo and citomni/image disagree original_name, size, mime; on disagreement also image_mime
InvalidImage invalid_image citomni/image rejects the content (ImageInputException) original_name, size, mime; the image exception is previous

PHP upload error codes

Code Result
UPLOAD_ERR_OK Continue
UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE Rejected as TooLarge
UPLOAD_ERR_PARTIAL Rejected as Partial
UPLOAD_ERR_NO_FILE Rejected as NoFile
UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION, unknown codes UploadStorageException naming the code: a server-side problem

citomni/image exceptions

Only the calls to inspect() and save() are wrapped, only these types are translated, and there is no catch-all around them:

From citomni/image In Upload
ImageInputException UploadRejectedException with the reason InvalidImage. No target was touched.
ImageWriteException, including ImageTargetExistsException UploadStorageException. The outputs committed() lists are removed first. An occupied target is never Upload's and is never touched.
\InvalidArgumentException from save() UploadConfigException naming the profile. Upload passes only the profile's specs and options and paths it built itself, so the cause is the profile.
ImageCapabilityException and anything else Propagates unchanged.

The original exception is previous in every translation. ImageCapabilityException means that the runtime cannot do what the profile asks, such as decoding or encoding a format or converting colors; see the deployment checklist.


Image outputs

Upload calls citomni/image only when the profile has an image block and the detected type is a citomni/image format. Plain profiles, other types in an image profile (a PDF, or an SVG, which is not an image format), deletion, cleanup, and the path methods never touch it.

  1. Inspect. citomni/image inspects the source. The type it reports must equal the detected type.
  2. Name and prepare. Upload names the main file and the variants and creates the target directory, because citomni/image requires existing directories.
  3. Image job. When the profile has outputs, one save() call gets the main spec (unless main is null) and every variant, each with the path Upload chose and without overwrite, plus the profile's options. file_mode is then applied to every output.
  4. Original. With main: null, Upload writes the original bytes once the job has succeeded.
  5. Result. The main file is hashed when the profile sets hash, and the result is assembled.

Type agreement

finfo is Upload's detector for every file, and citomni/image is authoritative for understanding images. Upload compares the two and implements no image detection of its own:

Order and atomicity

Compensation

When a store call fails, every file it created is removed before the exception propagates: Upload's own files, temporary files included, and citomni/image's outputs, which means all of them after a successful save() and exactly those in committed() after a failed publish. For storeUploads(), that includes every file of the entries the batch stored before the failure. Removal is keyed by output name and uses the paths Upload chose, so Upload never removes a path it did not name. It is best effort: a file the filesystem refuses to unlink stays and is logged when log is registered. citomni/image removes its own temporary files; whatever a crash leaves behind is removed by the sweep job in the deployment checklist.

Upload decodes nothing, reads no pixel or EXIF data, adds no temporary-file layer around save(), and runs no capability probes. capabilities() is a deployment and diagnostics tool, not part of the pipeline.


Storage and naming

Public and private storages

Path rules

StoragePath::validateRelative() validates every storage-relative path lexically: directory, $subdir, web_path, and every path given to the deletion and path methods.

Placement and names

Empty parts are skipped.

Original file name

The original name is metadata only (display, Content-Disposition). OriginalName::sanitize():

  1. normalizes \ to / and keeps the part after the last / (basename() is locale-dependent for multibyte input);
  2. scrubs invalid UTF-8 with mb_scrub();
  3. removes C0 and C1 control characters, DEL, and the bidi controls U+202A–U+202E and U+2066–U+2069;
  4. trims whitespace, Unicode whitespace included, with mb_trim();
  5. limits the name to 255 characters;
  6. returns file when nothing is left.

storeUpload() sanitizes the client's name, and storeLocal() sanitizes $originalName or the source path. Escaping the name for output is the template's job.

MIME types and extensions

The extension always follows the canonical type, never the client's file name.

A citomni/image format is stored with its ImageFormat value as the extension, except jpeg → jpg and tiff → tif. The set is derived from ImageFormat::cases(), so a format that citomni/image adds is covered without a change in Upload. With the current formats:

Other types follow a curated map; anything else, including HTML, JavaScript, PHP, and executables, is stored as bin:

Writes and directories


Deleting files and deriving paths

All eight methods validate every path against the path rules before any filesystem call, and '' is invalid as a file path. One invalid path means that nothing is deleted. Invalid paths throw \InvalidArgumentException.

delete*() and cleanup*()

delete(), deleteWithVariants() cleanup(), cleanupWithVariants(), cleanupStored()
Use when Deleting is the task itself and must have happened before anything else is recorded Removing files after a decision that is already committed
Missing file Counts as deleted Counts as removed
unlink() fails UploadStorageException Logged; the result becomes false; nothing is thrown
The path names a directory UploadStorageException false and a log entry
A link survives a refused unlink Not gone, even when its target is missing Not gone
Returns void: the files are gone, or it throws bool: true when every file is gone
Invalid or empty path \InvalidArgumentException before any IO \InvalidArgumentException before any IO, also for a malformed store result
Unknown storage or missing root UploadConfigException UploadConfigException; cleanupStored() checks every result's storage before it removes anything
No paths Checks the root, then does nothing Checks the root, then returns true; cleanupStored() without results returns true
Order As given; *WithVariants(): the variants in profile order, then the main file The same; cleanupStored(): per result, its variants in result order, then its main file
A file fails Stops and throws Attempts every path
Log context – storage, path (absolute), stored_path (the file's own relative path), error

Rule of thumb: when removing the file comes after a database commit, it is cleanup. That covers compensating a failed persistence, the old file after a replacement, and the file of an attachment the user deleted. When removing the file is the action itself and must have happened before the database is marked, it is delete. A retention purge, for example, deletes strictly and then marks the record as purged, so a failure leads to a retry instead of a record that lies.

Path methods

The path methods read configuration only: no filesystem call (no stat, no realpath()), no finfo, and no citomni/image. They therefore say nothing about whether a file exists.


Usage patterns

The patterns are illustrative, like the Quick start. The patterns that store through Upload share one order: store the new file, persist, then remove the old file. A crash can leave an orphaned file or a dot-prefixed .tmp file; that is harmless and is removed by an application job. A database reference to a deleted file must never arise.

One file per entity

The Quick start controller hands the stored avatar to an Operation that swaps the path and discards the previous files:

cleanupStored() and cleanupWithVariants() are best effort, so neither the compensation nor the removal after the commit needs a try/catch. The compensation removes exactly the files of the fresh result; the previous files are derived from the persisted path and the profile. A removal that fails after the commit is logged by Upload, and the action succeeds. The thumbnail path follows from variantPath($path, 'thumb', 'avatar'); applications that prefer a thumbnail column persist $stored['variants']['thumb']['path'].

Many files per entity, with a maximum

An all-or-nothing adapter:

For partial acceptance, the adapter skips the early return: it passes $batch['stored'] on and shows the messages for $batch['rejected'] with the outcome.

storeUploads() is a convenience, not a requirement. An adapter with a flow of its own, such as a profile per file or stopping at the first rejection, loops over storeUpload() and compensates with cleanupStored():

Private download and purge

A retention purge belongs in the application's Operations. It deletes strictly before it marks the record:

Intake for an application-owned storage protocol

Some applications must not let a file exist before their database knows it, for example records that have to be found after a crash without listing storage. They commit an intent first and write the bytes under a key derived from it. Upload's storage cannot serve them, because it names files itself, but checkUpload() applies the same intake policy in front of the application's own write:


Security

Upload's own guarantees:

Inherited from citomni/image and not retested here: metadata stripping (EXIF, XMP, GPS) on re-encoding, the pixel budget, truncation checks, multi-frame rejection, geometry verification, and no-clobber publishing.


Deployment checklist


Web server configuration for public storage

Upload does not own the public upload area or its server configuration; CitOmni's application scaffold provides the directory and its .htaccess. Upload relies on the area to:

Dangerous types are stored as bin, so Upload never writes a file with an executable extension; the first rule is defense in depth.

Apache, as an example to adapt:

nginx, as an example to adapt:


Current limitations

Known behavior:

Not provided:


Testing

The tests cover Upload's own logic and its boundary with citomni/image. Image correctness (orientation, geometry, alpha, compression, metadata, codecs, backends, color, multi-frame, output verification) is tested in citomni/image only.

The tests are standalone suites in plain PHP, one per area, each run in its own process. There is no PHPUnit and no Composer installation: tests/bootstrap.php loads Upload's src/ and tests/ and the sibling packages ../kernel/src and ../image/src, so the suites run in this development layout and fail with the searched paths when a sibling is missing:

An installation under vendor/citomni/ has the same shape, but tests/ is excluded from the Composer dist archive, so the suites need a source checkout.

Run every suite with:

composer test runs the same command. Run one suite with php tests/<suite>/run.php, for example php tests/storage/run.php.

A suite reports every case as PASS <case> on stdout, FAIL <case> - <reason> on stderr, or SKIP <case>: <reason> when the platform cannot run it. A failed case does not stop the suite. The last line holds the totals, N passed, M failed[, K skipped], and the exit code is 1 when a case failed. tests/run.php prints each suite under == <suite>/run.php and ends with the summed totals under == total. Each suite has a README with its cases, its expected totals, and the conditions under which a case is skipped.

Suite Covers
util StoragePath, OriginalName, MimeMap, UploadRejection, and the exception hierarchy
profile Every profile rule, the Registry baseline, memoization, inline profiles, and merge semantics
intake UploadedFiles, error-code mapping, check order, provenance, checkUpload() and storeUploads() without HTTP, rejectionMessage(), and the language files
storage Generic validation, plain writes, the result, routing, $move, and compensation
image-path The image path against a scripted citomni/image double: outputs, order, result fields, exception translation, and compensation
image-integration The real citomni/image: finfo against inspect() on crafted headers and encoded files, and the happy path with GD
delete delete*() and cleanup*(), cleanupStored() included, on an in-memory storage and on the real filesystem
read-path variantPath(), absolutePath(), webPath(), and proof that they do no IO
http-upload End to end through PHP's built-in web server: $_FILES shapes, storeUpload() with real uploads and error codes, image uploads, checkUpload() without writes, and storeUploads() batches with partial acceptance and compensation

The suites require PHP 8.5, ext-fileinfo, and ext-mbstring, and the image cases require ext-gd. Every unsuppressed diagnostic fails the case, including deprecations and #[\NoDiscard] warnings. Failures are forced through the filesystem or through stream wrappers in tests/Support/; the production code has no test seams. The package has no database suites and no multi-process cases, so neither CITOMNI_TEST_PASSWORD nor CITOMNI_TEST_PARALLEL changes a run.

Cases the platform cannot run report SKIP instead of failing:

PHP's built-in web server is not optional: when it cannot start on 127.0.0.1, every http-upload case that sends a request fails with the reason.

Mutation testing

tests/mutation/ holds a mutation runner and 88 mutants in the areas profile, store, intake, batch, image, delete, and path. The runner is named mutate.php, not run.php, so tests/run.php does not run it.


Coding & Documentation Conventions

All CitOmni projects follow the shared conventions documented here:

CitOmni Coding & Documentation Conventions


License

CitOmni Upload is open-source under the MIT License. See NOTICE.


Trademarks

"CitOmni" and the CitOmni logo are trademarks of Lars Grove Mortensen.

You may make factual references to "CitOmni", but do not modify the marks, create confusingly similar logos, or imply sponsorship, endorsement, or affiliation without prior written permission.

Do not register or use "citomni" or confusingly similar terms in company names, domains, social handles, or top-level vendor/package names.

For details, see TRADEMARKS.md.


Author

Developed by Lars Grove Mortensen (c) 2012-present.


CitOmni - low overhead, high performance, ready for anything.


All versions of upload with dependencies

PHP Build Version
Package Version
Requires php Version ^8.5
ext-fileinfo Version *
ext-mbstring Version *
citomni/kernel Version ^1.0.2.2
citomni/image Version ^1.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package citomni/upload contains the following files

Loading the files please wait ...