API / Minn\Media

Minn\Media

Uploads, image sizes and attachment metadata. 14 classes, 56 public methods and 22 internals. This page is the public surface; each class name opens the whole class.

AvifInfo final class

public/minn/src/Minn/Media/AvifInfo.php · 142 lines · Open the class →

An AVIF file's primary image as wp_get_avif_info reports it (probe media-tags): width and height from its spatial-extent property, bit depth and channel count from its pixel-information property. The file is an ISO base media file: an ftyp box naming the avif or avis brand, then a meta box whose primary item (pitm) is tied to its properties (ipco) by the association table (ipma). Anything else, or a file it cannot follow, is unknown in every field.

staticread(string $bytes): array

The primary image's size, bit depth and channels from a file's first bytes.

@return array{width: int|false, height: int|false, bit_depth: int|false, num_channels: int|false}
insideisAvif(), primaryItem(), associations(), boxes(), ordered()

Canvas final readonly class

public/minn/src/Minn/Media/Canvas.php · 168 lines · Open the class →

One GD bitmap and the operations the media layer needs on it. Every operation returns a new canvas; alpha is preserved throughout, which is what makes PNG and WebP sub-sizes match the reference's.

propreadonly int $width
propreadonly int $height
staticof(GdImage $image): self

A canvas over a GD image already in hand (what WP_Image_Editor_GD::_save is given).

methodimage(): GdImage

The GD image behind the canvas (what WP_Image_Editor_GD::_resize hands back).

staticopen(string $path): ?self

A canvas from an image file, or null when it cannot be decoded or would not fit in memory. The header names the dimensions before a pixel is decoded, so a small file that claims a huge canvas is refused instead of taking the request down.

methodresample(array $box): self

Resamples a source box onto a destination box.

@param array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int} $box dst x, dst y, src x, src y, dst w, dst h, src w, src h (the GD argument order)
methodcrop(int $x, int $y, int $width, int $height, ?int $targetWidth = NULL, ?int $targetHeight = NULL): self

A canvas cut to a rectangle, resized to a target when given.

methodrotate(float $angle): ?self

A canvas turned by an angle, or null when GD refuses.

methodflipVertical(): self

A canvas mirrored on either axis.

methodflipHorizontal(): self

A canvas mirrored left to right.

methodcopy(): self

An untouched copy of the canvas.

methodwrite(string $path, string $mime, int $quality): bool

Writes the bitmap in the given format; the directory is created when missing.

methodprogressive(): self

The bitmap written progressively from now on (an interlaced JPEG).

methodbaseline(): self

The bitmap written line by line from now on, as it is by default.

methodstream(string $mime, int $quality): bool

Writes the image to the output in a format, at a quality.

insideaffordable(), memoryLimit(), flipped(), encode()

FileTypes final class

public/minn/src/Minn/Media/FileTypes.php · 75 lines · Open the class →

File types as WordPress knows them (data/mime.json): every extension's MIME type, the ones a user may upload, the kind each extension is (image, audio, document...), and the type a file name's extension says, each list through the filter plugins hook.

statictable(): array

The table: mime (extensions => type), allowed (the ones uploads take by default) and ext2type (kind => extensions).

@return array<string, mixed>
staticall(): mixed

Every extension's MIME type (wp_get_mime_types), as mime_types leaves them.

staticallowed(mixed $user = NULL): mixed

The types a user (the request's, unless one is given) may upload (get_allowed_mime_types): HTML and JavaScript too for one with unfiltered_html; then upload_mimes.

staticcheck(mixed $filename, mixed $mimes = NULL): array

The extension and type a file name has among the types given (the allowed ones by default), false for each when none matches (wp_check_filetype).

@return array{ext: string|false, type: mixed}
statickinds(): mixed

The extensions of each kind (wp_get_ext_types), as ext2type leaves them.

statickindOf(mixed $ext): mixed

The kind an extension is (wp_ext2type), or null.

Icons final class

public/minn/src/Minn/Media/Icons.php · 68 lines · Open the class →

The icon a file type is shown with, as wp_mime_type_icon finds it (probe plugin-helpers): the icon folders (core's wp-includes/images/media, and any icon_dirs adds) read once a request into a name => address map, an icon of the asked kind (.png or .svg) preferred where both exist; then the first name the type answers to: an attachment's file extension and the kind of file it is, the type, its two halves, the type with an underscore for the slash. Nothing answering (a post that is not there, a type that is empty) is the default icon.

staticforType(string|int $mime, string $preferred): string|false

The icon's address for a type or an attachment id.

insidemap(), files()

ImageMime final class

public/minn/src/Minn/Media/ImageMime.php · 34 lines · Open the class →

An image file's real type from its first bytes, as the reference sniffs it before trusting an upload's extension (probe hook-inputs-2): PHP's own header check for the classic formats (JPEG, PNG, GIF, WebP, BMP, ICO, TIFF, AVIF), then an ISO media file's major brand for the HEIF family PHP does not know. A file too short or of another kind has no image type.

staticof(string $file): ?string

The image type the file's header declares, or null.

Kind final class

public/minn/src/Minn/Media/Kind.php · 79 lines · Open the class →

Whether an attachment is an image, audio, video, or a given extension, judged by its MIME type first and its file extension second.

constPOST_MIME_TYPES = array ( 'image' => array ( 0 => 'Images', 1 => 'Manage Images', 2 => 'Image <span class="count">(%s)</span>', 3 => 'Images <span class="count">(%s)</span>', ), 'audio' => array ( 0 => 'Audio', 1 => 'Manage Audio', 2 => 'Audio <span class="count">(%s)</span>', 3 => 'Audio <span class="count">(%s)</span>', ), 'video' => array ( 0 => 'Video', 1 => 'Manage Video', 2 => 'Video <span class="count">(%s)</span>', 3 => 'Video <span class="count">(%s)</span>', ), 'application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-word.document.macroEnabled.12,application/vnd.ms-word.template.macroEnabled.12,application/vnd.oasis.opendocument.text,application/vnd.apple.pages,application/pdf,application/vnd.ms-xpsdocument,application/oxps,application/rtf,application/wordperfect,application/octet-stream' => array ( 0 => 'Documents', 1 => 'Manage Documents', 2 => 'Document <span class="count">(%s)</span>', 3 => 'Documents <span class="count">(%s)</span>', ), 'application/vnd.apple.numbers,application/vnd.oasis.opendocument.spreadsheet,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel.sheet.macroEnabled.12,application/vnd.ms-excel.sheet.binary.macroEnabled.12' => array ( 0 => 'Spreadsheets', 1 => 'Manage Spreadsheets', 2 => 'Spreadsheet <span class="count">(%s)</span>', 3 => 'Spreadsheets <span class="count">(%s)</span>', ), 'application/x-gzip,application/rar,application/x-tar,application/zip,application/x-7z-compressed' => array ( 0 => 'Archives', 1 => 'Manage Archives', 2 => 'Archive <span class="count">(%s)</span>', 3 => 'Archives <span class="count">(%s)</span>', ), )

The media library's filter groups: a MIME pattern (a top-level type, or a comma list of full types) => its plural label, its manage label, and the singular and plural count labels.

staticmatchWildcards(array $patterns, array $reals): array

Real mime types grouped under the wildcard patterns that match them (image, audio/*, image/jpeg, *), the last pattern first; a pattern with no matches is omitted (probe post-remaining).

@param list<string> $patterns
@param list<string> $reals
@return array<string, list<string>>
staticmatches(string $type, string $mime, string $extension, array $audioExtensions, array $videoExtensions): bool

Whether a file is of a media type, by mime prefix or by extension list.

Metadata final class

public/minn/src/Minn/Media/Metadata.php · 112 lines · Open the class →

The _wp_attachment_metadata blob: parsed by scanning for the shapes it holds (top-level dims and file, the sizes map, the image_meta scalars) and written back in the reference's stored form: a:6 at the top, image_meta as a:13 with alt last.

staticnamesFile(string $location, array $meta): bool

Whether an image location names the attachment's file, one of its sizes, or its original (probe media-tags): the location, its query dropped, must end with the stored path, or with the stored folder and a size's or the original's file name. A bare file name with no folder matches nothing.

@param array<string, mixed> $meta the attachment metadata as stored
staticparse(?string $blob): array

The attachment metadata blob as an array, tolerant of anything missing.

@return array{width: int, height: int, file: string, filesize: int, sizes: array<string, array>, image_meta: ?array}
staticblankImageMeta(): array

The image_meta block a fresh upload carries.

PhotoMeta final class

public/minn/src/Minn/Media/PhotoMeta.php · 165 lines · Open the class →

A photo's own description, read as the reference's wp_read_image_metadata reads it (probe image-meta): IPTC first (headline or object name for the title, caption, credit or byline, copyright, keywords, the date and time it was made), then EXIF for what IPTC left empty (a short description as the title, the user comment or description as the caption, the artist, copyright, model, the digitized time, aperture, focal length, ISO, exposure, orientation). Text that is not UTF-8 is read as Latin-1, and every value is cleaned (the reference runs wp_kses_post) and made a string; a short caption doubles as the title.

constEXIF_TYPES = array ( 0 => 2, 1 => 7, 2 => 8, )

EXIF is read for these image types only: JPEG and TIFF, both byte orders.

staticread(string $file, array $exifTypes, Closure $clean): ?array

The metadata, the image type, the IPTC and EXIF it came from; null when there is no such file.

@param list<int> $exifTypes
@param Closure(string): string $clean
@return array{meta: array<string, mixed>, type: int|null, iptc: array<string, mixed>, exif: array<string, mixed>}|null
insidefromIptc(), fromExif(), described(), fraction(), exifTime(), cleaned()

Sizing final class

public/minn/src/Minn/Media/Sizing.php · 263 lines · Open the class →

The image size arithmetic the media functions share: the crop or scale a resize needs, the registered size a requested box picks, and the srcset candidates an image's sizes yield. Behaviour pinned by contracts/fixtures/api/media.json.

staticconstrain(int $width, int $height, int $maxWidth, int $maxHeight): array

The largest box inside the limits that keeps the ratio: only sides over their limit shrink, the smaller ratio wins, and a result one pixel short of a limit it was constrained by snaps to that limit.

@return array{0: int, 1: int}
staticresize(int $origW, int $origH, int $destW, int $destH, bool $crop, Closure $constrain): ?array

The GD-style resize box (dst x, dst y, src x, src y, dst w, dst h, src w, src h), or null when every asked dimension is larger than the image (nothing to make).

@param Closure(int, int, int, int): array{0: int, 1: int} $constrain scales a box into a maximum
@return array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int}|null
staticsameSize(array $box, int $origW, int $origH): bool

Whether a resize box makes the image's own size again, a pixel either way counting as rounding.

@param array{0: int, 1: int, 2: int, 3: int, 4: int, 5: int, 6: int, 7: int} $box
staticintermediate(array $meta, array|string $size, ?string $fileUrl, Closure $editorConstrain, Closure $matchesRatio): ?array

The registered size that serves a request: by name, or for a requested box the size of exactly that box, else the smallest larger one in its proportion (the full size never counts), else the thumbnail when it is wide enough; with its path and URL beside the file's.

@param array<string, mixed> $meta attachment metadata
@param string|array{0: int, 1: int} $size
@param Closure(int, int, array): array{0: int, 1: int} $editorConstrain
@param Closure(int, int, int, int): bool $matchesRatio
@return array<string, mixed>|null
staticsources(array $sizeArray, string $src, array $meta, Closure $matchesRatio, string $uploadsUrl, Closure $maxWidth): array

The srcset candidates by width: every size on the image's ratio (and the same edit, when the file was edited) no wider than the cap, the source itself winning a width clash and passing the cap.

@param array{0: int, 1: int} $sizeArray
@param Closure(int, int, int, int): bool $matchesRatio
@param Closure(array{0: int, 1: int}): int $maxWidth the widest candidate (max_srcset_image_width; 0 for no cap), asked before the folder is checked
@return array<int, array{url: string, descriptor: string, value: int}>
staticsrcset(array $sources): ?string

The srcset attribute for at least two sources; null for fewer.

staticeditorSizes(array $meta, string $baseUrl, string $fullUrl, array $names, Closure $downsize): array

The image sizes an attachment offers the editor, each with orientation. @param Closure(string): (array|false) $downsize

@param Closure(string): (array|false) $downsize
staticoldThumbnail(string $imagefile, string $thumb): ?array

The old thumbnail an image's metadata names (its `thumb`), as a file beside the image with its size, while it is still there (probe image-downsize).

@return array{0: string, 1: int, 2: int}|null the file's name, width and height
insidecropBox(), join()

Upload final readonly class

public/minn/src/Minn/Media/Upload.php · 89 lines · Open the class →

One file arriving for the library, on either transport: a multipart field named "file" (a temporary upload to move) or a raw body whose Content-Disposition names the file. The parent is read from the same request, wherever the client put it.

new__construct(string $filename, ?string $movedFrom, ?string $raw, int $parent = 0)
propreadonly string $filename
propreadonly ?string $movedFrom
propreadonly ?string $raw
propreadonly int $parent
staticfromRequest(Minn\Http\Request $request): ?self

Null when the request carries no file.

staticfilenameFrom(array $headers): ?string

The file a Content-Disposition names, as the reference reads one (probe rest-attachments-controller): the parameters after the disposition type, split on semicolons as written; the last non-empty filename= of all the headers, unquoted when double quotes wrap it. "filename" is matched as written, and filename* is not read. Null when none names one.

@param list<string> $headers
staticrefusal(string $data, array $headers): ?Minn\RestError

Why a raw body cannot be taken, in the reference's order: no data, no Content-Type, no Content-Disposition, one naming no file, a Content-MD5 the data does not match. Null when it can. The headers are the REST request's, keyed by lower-case names with underscores.

@param array<string, list<string>> $headers
methodmime(): ?string

The mime type the extension maps to, or null for one the library refuses.

staticparentOf(Minn\Http\Request $request): int

The post the client wants the file attached to: form field, query, or JSON body.

Uploads final readonly class

public/minn/src/Minn/Media/Uploads.php · 135 lines · Open the class →

The uploads directory: paths, URLs, the allowed types, and landing a file.

constMIMES = array ( 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 'webp' => 'image/webp', 'pdf' => 'application/pdf', 'txt' => 'text/plain', 'mp4' => 'video/mp4', 'mp3' => 'audio/mpeg', 'zip' => 'application/zip', )

extension => canonical mime

new__construct(Minn\Front\Permalinks $permalinks, string $baseDir)
methodbaseDir(): string

The uploads directory on disk.

methodbaseUrl(): string

The uploads directory's URL.

methodurlFor(string $relativePath): string

The URL of a file by its relative path.

methodpathFor(string $relativePath): string

The path of a file by its relative path.

staticsanitizeName(string $filename): string

A safe file name: one extension. Every dot-separated part between the base and the last extension that is not itself an allowed extension is joined with an underscore, so "shell.php.png" lands as "shell_php.png" and no handler can be talked into running it.

methodrelativeOf(string $absolutePath): ?string

A stored file's path relative to the uploads folder, or null for a file outside it.

staticattachmentFiles(string $file, array $meta, ?array $backupSizes): array

Every file an attachment owns, in deletion order: the legacy thumb, each sub-size, the original image, the backup sizes, then the file itself.

@param array<string, mixed> $meta the attachment metadata
@param array<string, array<string, mixed>>|null $backupSizes
@return list<string> absolute paths, unique, only the main file checked for existence
staticlayout(string $uploadPath, string $uploadUrlPath, string $siteUrl, string $contentDir, string $contentUrl, string $abspath, bool $yearMonth, string $time): array

Where uploads live, from the site's options: the default folder under wp-content, a relative upload_path under ABSPATH, or an absolute one; the URL likewise, with year/month subfolders when the site asks.

@return array{path: string, url: string, subdir: string, basedir: string, baseurl: string, error: false}

WebpInfo final class

public/minn/src/Minn/Media/WebpInfo.php · 24 lines · Open the class →

A WebP file's size and kind from its first 40 bytes, as wp_get_webp_info reads them (probe plugin-queue8): a simple lossy frame (VP8), a lossless one (VP8L), or the extended form (VP8X), which the reference names animated-alpha whatever its flags say. Anything shorter or of another chunk is unknown.

staticread(string $head): array

The width, height and kind a file's first bytes declare. @return array{width: int|false, height: int|false, type: string|false}

@return array{width: int|false, height: int|false, type: string|false}

Writer final readonly class

public/minn/src/Minn/Media/Writer.php · 24 lines · Open the class →

Where the media library's files live, as a REST upload names them: a stored file's address, its path on disk, and its path relative to the uploads folder. The attachment itself is written through the runtime (wp_handle_upload, wp_insert_attachment, wp_generate_attachment_metadata), as the reference writes it.

new__construct(Minn\Media\Uploads $uploads)
methodurlOf(string $relative): string

A stored file's address.

methodpathOf(string $relative): string

A stored file's path on disk.

methodrelativeOf(string $absolutePath): ?string

A stored file's path relative to the uploads folder, or null for one outside it.