ImageUploadPolicy
in package
Single policy for raster images from a user: the front form (avatar), the dropzone and the Api (gallery via {@see \App\UI\Api\System\Models\Helpers\Assets\Processors\RasterImageProcessor}, avatar via {@see \App\Core\Traits\Api\Models\Managers\AssetTrait}).
- Image bomb: a 20000x20000 png is a few kB, but
GDwould allocate about 1.6 GB and the save would die with "Allowed memory size exhausted". Dimensions are therefore read from the header (getimagesizefromstring, nothing is decoded) and a file over the ceiling is rejected beforeGDopens it: MAX_PIXELS and a memory estimate against the free part ofmemory_limit. - Exif/
GPS: buildSanitized() decodes the image, rotates it by the ExifOrientation(otherwise a phone photo would lie on its side once the metadata is gone) and stores it again without metadata. - Avatar: the type is determined from the content, not the extension; SVG and other active content do not belong here.
Everything is static and stateless (like AttachmentFilePolicy).
Table of Contents
Constants
- ALLOWED_EXTENSIONS : array<string|int, mixed> = ['jpg', 'jpeg', 'png', 'gif', 'webp']
- Extensions without the dot that the policy accepts (by content, see {@see EXTENSION_BY_TYPE}).
- MAX_PIXELS : int = 40000000
- Ceiling of the image size in pixels (width x height); 40 Mpx covers common phone and dslr photos.
- MAX_PIXELS_LABEL : string = '40 Mpx'
- Label of the ceiling for form messages (`%size%`).
- REASON_DIMENSIONS : string = 'dimensions'
- REASON_MEMORY : string = 'memory'
- REASON_TYPE : string = 'type'
- EXTENSION_BY_TYPE : array<string|int, mixed> = [IMAGETYPE_JPEG => 'jpg', IMAGETYPE_PNG => 'png...
- Type detected from the content to the extension of the stored file.
- GD_BYTES_PER_PIXEL : int = 4
- `GD` holds a truecolor image as 4 bytes per pixel whatever the channel count of the source; the coefficient covers the `GD`/libjpeg/libpng overhead and a small resize target. Rotation by Exif needs a second full copy, which {@see buildSanitized()} checks separately.
- GD_OVERHEAD : float = 1.3
- MEMORY_RESERVE : int = 16 * 1024 * 1024
- Memory that must remain after decoding (rest of the request, base64 copy of the data).
- REENCODE_QUALITY : int = 92
- jpeg/WebP quality when re-encoding the original: visually identical, metadata gone.
Methods
- buildSanitized() : string|null
- Returns the image stored again without metadata (Exif including `GPS`, `XMP`, `IPTC`, comments) and with the orientation straightened. `$targetExtension` sets the output format (the extension the file will be stored under, so content and extension always match); `null` means the format by content.
- getDimensionRejection() : string|null
- The dimension part of the policy on its own; {@see \App\UI\Api\System\Models\Helpers\Assets\VariantRenderer} also calls it for a video poster (jpeg from ffmpeg), where the type needs no check.
- getRejectionReason() : string|null
- Returns `null` when the image may go to `GD`, otherwise the rejection reason ({@see REASON_TYPE}, {@see REASON_DIMENSIONS}, {@see REASON_MEMORY}). Nothing is decoded; only the header is read.
- resolveExtension() : string|null
- Returns the extension by content (`jpg`/`png`/`gif`/`webp`), `null` for a disallowed or unreadable type.
- validateUploadControl() : bool
- Nette form validator (`addRule([ImageUploadPolicy::class, 'validateUploadControl'], ...)`).
- applyExifOrientation() : GdImage
- Maps the Exif `Orientation` (1-8) to a rotation/flip of the pixels. It must run before the image is stored again, because `GD` does not write the metadata and a browser would then show the image as it lies.
- computeAvailableMemory() : int|null
- Returns the free memory up to `memory_limit`; `null` means no limit (`-1`).
- computeDecodeBytes() : int
- encode() : string|null
- normalizeExtension() : string|null
- parseIniBytes() : int
- readExifOrientation() : int
- Reads the Exif `Orientation` from jpeg bytes (1 means unchanged, also when Exif is missing or unreadable).
- readHeader() : array{width: int, height: int, type: int, extension: string}|null
Constants
ALLOWED_EXTENSIONS
Extensions without the dot that the policy accepts (by content, see {@see EXTENSION_BY_TYPE}).
public
array<string|int, mixed>
ALLOWED_EXTENSIONS
= ['jpg', 'jpeg', 'png', 'gif', 'webp']
MAX_PIXELS
Ceiling of the image size in pixels (width x height); 40 Mpx covers common phone and dslr photos.
public
int
MAX_PIXELS
= 40000000
MAX_PIXELS_LABEL
Label of the ceiling for form messages (`%size%`).
public
string
MAX_PIXELS_LABEL
= '40 Mpx'
REASON_DIMENSIONS
public
string
REASON_DIMENSIONS
= 'dimensions'
REASON_MEMORY
public
string
REASON_MEMORY
= 'memory'
REASON_TYPE
public
string
REASON_TYPE
= 'type'
EXTENSION_BY_TYPE
Type detected from the content to the extension of the stored file.
private
array<string|int, mixed>
EXTENSION_BY_TYPE
= [IMAGETYPE_JPEG => 'jpg', IMAGETYPE_PNG => 'png', IMAGETYPE_GIF => 'gif', IMAGETYPE_WEBP => 'webp']
GD_BYTES_PER_PIXEL
`GD` holds a truecolor image as 4 bytes per pixel whatever the channel count of the source; the coefficient covers the `GD`/libjpeg/libpng overhead and a small resize target. Rotation by Exif needs a second full copy, which {@see buildSanitized()} checks separately.
private
int
GD_BYTES_PER_PIXEL
= 4
GD_OVERHEAD
private
float
GD_OVERHEAD
= 1.3
MEMORY_RESERVE
Memory that must remain after decoding (rest of the request, base64 copy of the data).
private
int
MEMORY_RESERVE
= 16 * 1024 * 1024
REENCODE_QUALITY
jpeg/WebP quality when re-encoding the original: visually identical, metadata gone.
private
int
REENCODE_QUALITY
= 92
Methods
buildSanitized()
Returns the image stored again without metadata (Exif including `GPS`, `XMP`, `IPTC`, comments) and with the orientation straightened. `$targetExtension` sets the output format (the extension the file will be stored under, so content and extension always match); `null` means the format by content.
public
static buildSanitized(string $bytes[, string|null $targetExtension = null ]) : string|null
A gif is returned unchanged: it carries no Exif and GD would keep only the first frame of an
animation.
Returns null when the image failed the policy or GD cannot read it (the caller rejects it).
Parameters
- $bytes : string
- $targetExtension : string|null = null
Return values
string|nullgetDimensionRejection()
The dimension part of the policy on its own; {@see \App\UI\Api\System\Models\Helpers\Assets\VariantRenderer} also calls it for a video poster (jpeg from ffmpeg), where the type needs no check.
public
static getDimensionRejection(int $width, int $height) : string|null
Parameters
- $width : int
- $height : int
Return values
string|nullgetRejectionReason()
Returns `null` when the image may go to `GD`, otherwise the rejection reason ({@see REASON_TYPE}, {@see REASON_DIMENSIONS}, {@see REASON_MEMORY}). Nothing is decoded; only the header is read.
public
static getRejectionReason(string $bytes) : string|null
Parameters
- $bytes : string
Return values
string|nullresolveExtension()
Returns the extension by content (`jpg`/`png`/`gif`/`webp`), `null` for a disallowed or unreadable type.
public
static resolveExtension(string $bytes) : string|null
Parameters
- $bytes : string
Return values
string|nullvalidateUploadControl()
Nette form validator (`addRule([ImageUploadPolicy::class, 'validateUploadControl'], ...)`).
public
static validateUploadControl(UploadControl $control) : bool
An empty field passes; requiredness is handled by setRequired() or the FILLED condition.
Parameters
- $control : UploadControl
Return values
boolapplyExifOrientation()
Maps the Exif `Orientation` (1-8) to a rotation/flip of the pixels. It must run before the image is stored again, because `GD` does not write the metadata and a browser would then show the image as it lies.
private
static applyExifOrientation(GdImage $image, int $orientation) : GdImage
Parameters
- $image : GdImage
- $orientation : int
Return values
GdImagecomputeAvailableMemory()
Returns the free memory up to `memory_limit`; `null` means no limit (`-1`).
private
static computeAvailableMemory() : int|null
Return values
int|nullcomputeDecodeBytes()
private
static computeDecodeBytes(int $width, int $height) : int
Parameters
- $width : int
- $height : int
Return values
intencode()
private
static encode(GdImage $image, string $extension) : string|null
Parameters
- $image : GdImage
- $extension : string
Return values
string|nullnormalizeExtension()
private
static normalizeExtension(string $extension) : string|null
Parameters
- $extension : string
Return values
string|nullparseIniBytes()
private
static parseIniBytes(string $value) : int
Parameters
- $value : string
Return values
intreadExifOrientation()
Reads the Exif `Orientation` from jpeg bytes (1 means unchanged, also when Exif is missing or unreadable).
private
static readExifOrientation(string $bytes) : int
Parameters
- $bytes : string
Return values
intreadHeader()
private
static readHeader(string $bytes) : array{width: int, height: int, type: int, extension: string}|null
Parameters
- $bytes : string