API / Minn\Ops / Packages

Packages final readonly class

Minn\Ops\Packages · public/minn/src/Minn/Ops/Packages.php · 481 lines

Putting plugins, themes and extensions on disk. Plugins and themes come from the directory (wordpress.org's, asked through the Minn update service, Ops\Directory) or an uploaded zip, and are installed through WordPress's upgraders (UpgraderRun), so plugins hear every upgrader hook; a Minn extension (a folder with a minn.json) is placed by the engine. Every archive passes Archive's checks first: exactly one top-level folder that is a plain name (never "." or ".."), no absolute or dotted paths, no symbolic links, bounded entry count and size. Removal proves the folder is a direct child of its kind's directory before anything is deleted.

Constants and properties

privateTHEMES_INFO = 'https://updates.minn.run/v1/themes/info/1.2/'

The largest archive fetched or unpacked, in bytes.

privatePLUGINS_INFO = 'https://updates.minn.run/v1/plugins/info/1.2/'
privateINFO_OPTION = 'minn_plugin_info'
privateINFO_TTL = 43200
privatereadonly Minn\Content\Site $site

Promoted from the constructor.

privatereadonly string $contentDir

Promoted from the constructor.

privatereadonly Minn\Http $http

Promoted from the constructor.

Public

new__construct(Minn\Content\Site $site, string $contentDir, Minn\Http $http = \Minn\Http::__set_state(array( 'within' => array ( ), 'fake' => NULL, 'refusal' => NULL, )))#
methodsearchThemes(string $query): array#

Directory theme search, or the popular list for an empty query. @return list<array>

@return list<array>
methodsearchPlugins(string $query, int $page): array#

Directory plugin search: twelve per page with icons, short descriptions, install counts, and ratings, plus which results are already installed (by folder). @return array{plugins: list<array>, page: int, pages: int, total: int}

@return array{plugins: list<array>, page: int, pages: int, total: int}
methodpluginInfo(string $slug): array#

The slim card for one directory plugin, cached twelve hours per slug.

methodinstallPlugin(string $slug, string $version = ''): string#

Installs a directory plugin by slug, through the upgrader; returns its folder.

methodreplacePlugin(string $slug, string $version = ''): string#

Installs a directory plugin over the folder already there.

methoddirectoryPlugin(string $slug): ?array#

One directory plugin record, or null when the slug is unknown.

@return array<string, mixed>|null
methodinfoAction(string $kind, string $action, array $request): ?array#

The directory's answer to a plugins_api() or themes_api() action ($kind 'plugins' or 'themes'), its request passed as given (Ops\DirectoryApi), read whatever the status: a refusal is the directory's own words ({"error": ...}); an answer that is not JSON comes back as {"unexpected": body}. Null when the service did not answer.

$requestarray<string, mixed>
@return array<string, mixed>|null
methodqueryThemes(string $search, int $page, int $perPage): array#

A page of directory themes for `wp theme search`.

@return array{items: list<array<string, mixed>>, total: int}
methodqueryPlugins(string $search, int $page, int $perPage): array#

A page of directory plugins for `wp plugin search`.

@return array{items: list<array<string, mixed>>, total: int}
methoddirectoryTheme(string $slug): ?array#

One directory theme record, or null when the slug is unknown.

@return array<string, mixed>|null
methodinstallTheme(string $slug, string $version = ''): string#

Installs a directory theme by slug, through the upgrader; returns its stylesheet folder.

methodreplaceTheme(string $slug, string $version = ''): string#

Installs a directory theme over the folder already there.

methodunpack(string $zip, string $kind): array#

Unpacks an uploaded or downloaded archive into wp-content/themes or wp-content/plugins: a theme or a WordPress plugin through the upgrader, a Minn extension (minn.json) placed by the engine; refused when its folder is taken. @return array{folder: string, name: string, version: string, kind: string}

@return array{folder: string, name: string, version: string, kind: string}
methodunpackReplacing(string $zip, string $kind): array#

Unpacks a zip over a folder already there, replacing it whole.

methodremove(string $kind, string $folder): void#

Removes a theme or plugin folder that is not in use.

methodidentity(string $dir, string $kind): array#

What a folder holds by its headers; kind "unknown" when nothing identifies it. @return array{name: string, version: string, kind: string}

@return array{name: string, version: string, kind: string}
methodfetch(string $url, string ...$hostPrefixes): string#

A package over https, every redirect hop included, refusing anything else; when host prefixes are given, every hop must start with one. The request names the engine, never the site's address.

Internals 8

Private and protected methods: how the class does its work. Not a contract; they change whenever the code is tidied.

privatepluginPackage(string $slug, string $version): string#

The download link of a directory plugin (on the update service), at a version when one is asked for.

privateplain(string $value): string#

Directory text as the app shows it: tags stripped, entities decoded.

privatethemePackage(string $slug, string $version): string#

The download link of a directory theme (on the update service), at a version when one is asked for.

privateupload(string $zip, string $kind, array $args): ?array#

An archive checked by Archive and installed through the upgrader, as Minn Admin's uploads are on WordPress; null for a Minn extension, which the engine places itself.

$argsarray<string, mixed>the upgrader's install arguments
@return array{folder: string, name: string, version: string, kind: string}|null
privateplace(string $zip, string $kind, Closure $onExisting): array#

Unpacks a zip into wp-content; $onExisting decides what happens to a folder already at the destination (refuse, or clear it).

$onExistingClosure(string, string, array): mixed
privatecontained(string $kind, string $folder): string#

The path a folder of this kind lives at, proven to be a direct child of the kind's directory.

privateidentify(string $dir, string $kind): array#

What a folder is: a theme (style.css with a Theme Name), a Minn extension (minn.json), or a WordPress plugin, which is named so the refusal can say what was uploaded. @return array{name: string, version: string, kind: string}

@return array{name: string, version: string, kind: string}
privateask(string $url): string#

A directory answer from the Minn update service, every redirect hop staying on it.