CoolCMS API

ImageUploadPolicy
in package

FinalYes

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 GD would 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 before GD opens it: MAX_PIXELS and a memory estimate against the free part of memory_limit.
  • Exif/GPS: buildSanitized() decodes the image, rotates it by the Exif Orientation (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'

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

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|null

getDimensionRejection()

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|null

getRejectionReason()

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|null

resolveExtension()

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|null

validateUploadControl()

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
bool

applyExifOrientation()

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
GdImage

computeAvailableMemory()

Returns the free memory up to `memory_limit`; `null` means no limit (`-1`).

private static computeAvailableMemory() : int|null
Return values
int|null

computeDecodeBytes()

private static computeDecodeBytes(int $width, int $height) : int
Parameters
$width : int
$height : int
Return values
int

encode()

private static encode(GdImage $image, string $extension) : string|null
Parameters
$image : GdImage
$extension : string
Return values
string|null

normalizeExtension()

private static normalizeExtension(string $extension) : string|null
Parameters
$extension : string
Return values
string|null

parseIniBytes()

private static parseIniBytes(string $value) : int
Parameters
$value : string
Return values
int

readExifOrientation()

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
int

readHeader()

private static readHeader(string $bytes) : array{width: int, height: int, type: int, extension: string}|null
Parameters
$bytes : string
Return values
array{width: int, height: int, type: int, extension: string}|null
On this page

Search results