Skip to main content
Version: 4.0 preview

File

Version
4.0 preview
Updated
View markdown

An uploaded file of any type: the file goes on disk under dirBase, the filename goes in the column. The same machinery as image minus the image processing — no dimension checks, no mutations.

Storage kind: column · Column: varchar(255)

Use it for assets that are stored and served as-is: a PDF datasheet, a CAD file, a connector's certificate, a downloadable attached to an answer. Reach for image instead whenever the file is a picture, even if you do not need mutations today — the dimension checks and the option of adding mutations later come free, and switching type afterwards means re-uploading.

Settings

SettingMeaning
dirBaseRequired. Filesystem directory written to. Created (recursively, 0777) if missing
urlBaseURL base served from. Without it, reads expose no _href
filenameFixed base filename instead of the uploaded one
appendSerialAppend a random 4-digit serial to the base name — a cache-buster
allowedExtensionsArray of permitted extensions, lower-case, without the dot
allowedMimeTypesArray of permitted MIME types — checked as well as the extension
maxFileSizeKbUpload ceiling in KB — this is what size meant on a file property
optionsFILENAME_TO_RECORD_ID, NODELETEFILE, NO_SAVE_FILENAME

The filename is persisted by default

As with image, store() calls updateFilenameBaseTable() unless the definition opts out with NO_SAVE_FILENAME.

This used to be opt-in, behind a SAVE_FILENAME tag. Omitting it uploaded the file and never touched the column, and because prepareForStorage() has already put the previous filename into the data object, the record simply kept pointing at the old file — no error, nothing in a log. Every shipped file definition set the tag, so it was never really optional.

FILENAME_TO_RECORD_ID names the stored file after the record id; NODELETEFILE suppresses the "delete file at save" checkbox. The checkbox posts <propertyName>-delete=1, and it only renders when a file is stored, NODELETEFILE is absent, the property is not required, and the record id is not 0. A delete flag arriving when the property is not deletable makes store() throw.

Validation

check() refuses on the first problem it finds, unlike image, which collects them all:

CheckedCondition
PresenceOnly when required, the field applies, and the record is new (empty($data->id))
maxFileSizeKbfilesize() of the temp file against the ceiling
allowedMimeTypesKenedoFileHelper::getMimeType(). Skipped silently if the system cannot determine one
allowedExtensionsThe uploaded name's extension

Declare both allowedExtensions and allowedMimeTypes. An extension is a claim by the client; the MIME check looks at what was actually sent — but it is also the check that can silently not run, so neither alone is enough.

Note that the required-file check only fires on an insert. On an update, an existing record with a missing file is not re-flagged.

How the filename is built

Same order as image: FILENAME_TO_RECORD_ID → the definition's filename → the uploaded name, lower-cased and stripped of its extension. Then appendSerial appends - plus a random 4-digit number, and the uploaded extension is put back.

On a copy there is one difference from image: without appendSerial, file avoids a collision by probing -1, -2, … until the name is free on disk. image has no such loop, so an image copy without appendSerial can overwrite — though not in the shipped configuration, where every image property sets either appendSerial or FILENAME_TO_RECORD_ID, and the new record's id cannot collide.

What reads produce

KeyHolds
<name>the filename
<name>_hrefurlBase + / + filename, when urlBase is set
<name>_pathdirBase + / + filename, when dirBase is set

Unlike image, these keys are added whenever the bases are configured — they are set to '' for a record with no file rather than being left off.

size meant two different things

On a file property size was a kilobyte ceiling; on a text property it was an input's maxlength. One key, two unrelated meanings, disambiguated only by the type. They are now maxFileSizeKb and maxLength, with a shim in modernizeOldPropertySettings() routing an old size by type.

Notes

  • Deleting a record does delete the file. KenedoPropertyFile::delete() unlinks it before the row goes, and a failure to unlink is logged rather than raised — the record still goes. You do not need an afterDelete() for this.
  • The upload is rename()d out of the temp directory, not copied the way image does it, then chmod'ed 0775. On hosts where the temp dir is on a different filesystem that is the faster path.
  • Replacing a file unlinks the previous one, but only when the new name differs. With a fixed filename and no appendSerial the name is the same and the file is simply overwritten.
  • Copying a record whose source file is missing logs a warning and continues with an empty value — it still writes the row, because a storeExternally file property may be the only column in its foreign table and a row has to exist. image throws in the same situation.
  • storeExternally moves the filename column to another table; the property implements the branch itself. The files always live under dirBase.

@see image.md @see ../com_configbox_property_types.md §3 for storeExternally