API / Minn\Rest

Minn\Rest

The wp/v2 surface: shapes and controllers. 85 classes, 422 public methods and 251 internals. This page is the public surface; each class name opens the whole class.

Api final readonly class

public/minn/src/Minn/Rest/Api.php · 454 lines · Open the class →

The REST API: wires the controllers for one request and dispatches a route. Errors thrown anywhere underneath become the reference's error payload under the reference's headers.

new__construct(Minn\Http\Request $request, Minn\Http\Router $router, Minn\Rest\Services $services, Minn\Rest\Embed $embed)
methodroutes(): array

The engine's routes in the reference's index form.

@return array<string, list<string>> the engine's routes in the reference's form, route => methods
staticforRequest(Minn\Db $db, Minn\Http\Request $request): self

The API for one request: the shared services and the route table.

methodcaller(): Minn\Rest\Caller

Who is making this request.

methodlistPosts(string $route, string $type, Closure $shape, ?WP_REST_Request $as = NULL): Minn\Http\Response

A post type's list as its route serves it (the caller, the statuses, the parameters, the query, the totals and page links), each post given the shape asked for: the facade's controller class shapes them itself.

@param \Closure(\Minn\Content\PostRecord, Context): array<string, mixed> $shape
methodrevisionObject(array $row, Minn\Rest\Context $context): array

A revision (or autosave) row as wp/v2 shapes it, in the view or edit context. @param array<string, mixed> $row

@param array<string, mixed> $row
methodsaveAutosave(int $parentId, int $userId, string $title, string $content, string $excerpt): int

Saves a user's autosave of a post in its one slot per author; the revision's id.

methodpostObject(): Minn\Rest\PostObject

The wp/v2 post shape.

methodtermObject(): Minn\Rest\TermObject

The wp/v2 term shape.

methodcommentObject(): Minn\Rest\CommentObject

The wp/v2 comment shape.

methodmenuObject(): Minn\Rest\MenuObject

The wp/v2 menu shape.

methodmenuItemView(int $id, string $context): array

One menu item as wp/v2 shapes it in a context (view, edit or embed); empty when there is none. @return array<string, mixed>

@return array<string, mixed>
methodmediaView(int $id, string $context): array

One attachment as wp/v2 shapes it in a context (view, edit or embed), links included; empty when there is none. @return array<string, mixed>

@return array<string, mixed>
methodmediaLinks(int $id, string $context): array

One attachment's wp/v2 links in a context, without rendering the rest of it; empty when there is none. @return array<string, mixed>

@return array<string, mixed>
methodtemplateView(Minn\Theme\TemplateRecord $record, string $context, string $as): array

A template as wp/v2 shapes it in a context, for the route's own type (templates or template parts). @return array<string, mixed>

@return array<string, mixed>
methodthemeFields(WP_Theme $theme, WP_REST_Request $request): array

A theme in the wp/v2 shape, links included, before rest_prepare_theme. @return array<string, mixed>

@return array<string, mixed>
methodembedLinks(array $data, array $rels): array

A response's data with its embeddable links answered in-process as this caller; every rel when none is named. @param list<string> $rels @return array<string, mixed>

@param list<string> $rels @return array<string, mixed>
methoduserObject(): Minn\Rest\UserObject

The wp/v2 user shape.

methodtypes(): Minn\Rest\Types

The post types the surface knows.

methodactingAs(int $userId, string $token): self

Runs the request as a user already proven by the outer request, for the in-process calls plugin code makes, or as the user plugin code settled (determine_current_user; 0 for nobody); a user who no longer exists leaves the caller as the request itself resolves it.

methodhandle(string $route): Minn\Http\Response

Resolves a REST route (from the path or from ?rest_route=) to a response.

methodwithServerHeaders(Minn\Http\Response $response, string $route): Minn\Http\Response

The headers the reference's server adds to every REST answer over HTTP (probe rest-server-headers): X-Robots-Tag noindex always; and, while rest_send_cors_headers stays hooked, for a request from an Origin the CORS headers echoing it with Vary: Origin, otherwise Vary: Origin for a GET from someone not signed in. A signed-in caller's answer also carries the no-cache headers (rest_send_nocache_headers, nocache_headers). The CORS header lists go through rest_exposed_cors_headers and rest_allowed_cors_headers, handed the route's request.

methodwithDiscovery(Minn\Http\Response $response): Minn\Http\Response

A REST answer as it leaves over HTTP: pointing at the API's root, unless it carries a Link header of its own (a batch's parts do not).

methodwritePost(string $verb, string $route, string $type, string $base, string $id = '', ?WP_REST_Request $as = NULL): Minn\Http\Response

A post of a REST type the engine has no route for (one under a namespace of its own, probe namespaced-type) created, updated or deleted by the engine's own writer, for the type's controller, whose permission checks have already run.

methodhandleEngineOnly(string $route, ?WP_REST_Request $as = NULL): ?Minn\Http\Response

The engine's own answer to a route, or null when no engine route takes it; the runtime's table is never consulted. This is what the runtime's server calls for a core route, so a route the engine declines cannot bounce between the two. $as is the caller's own request object, which the route's parameters are set on.

methodallowedOn(string $route, ?WP_REST_Request $as = NULL): array

The methods this caller may use on one of the engine's routes, as its Allow header lists them (rest_send_allow_header asks).

@return list<string>
insidecontrollers(), engineResponse(), withPageLinks(), options(), withSchema(), withAllow()

ApplicationPasswordsController final readonly class

public/minn/src/Minn/Rest/ApplicationPasswordsController.php · 177 lines · Open the class →

wp/v2/users/{id}/application-passwords: list, create, rename, delete, and introspect the password that authenticated the call. Refusals and shapes follow the reference: 501 when passwords are unavailable (no HTTPS), 401 anonymous, 404 for an unknown user or password, arguments checked before permissions.

new__construct(Minn\Content\Users $users, Minn\Content\Site $site, Minn\Auth\ApplicationPasswords $passwords, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller, Minn\Rest\Schema $schema)
methodlist(Minn\Http\Request $request, string $userId): Minn\Http\ResponseGET /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords (cap edit_user on {user_id}; user {user_id} must exist)

The user's application passwords.

methodcreate(Minn\Http\Request $request, string $userId): Minn\Http\ResponsePOST /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords (cap edit_user on {user_id}; user {user_id} must exist)

Mints one; the plain password is in this answer only.

methoddeleteAll(Minn\Http\Request $request, string $userId): Minn\Http\ResponseDELETE /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords (cap edit_user on {user_id}; user {user_id} must exist)

Removes every one.

methodintrospect(Minn\Http\Request $request, string $userId): Minn\Http\ResponseGET /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/introspect (cap edit_user on {user_id}; user {user_id} must exist)

The password the current Basic auth session used.

methodsingle(Minn\Http\Request $request, string $userId, string $uuid): Minn\Http\ResponseGET /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/{uuid:[\w\-]+} (cap edit_user on {user_id}; user {user_id} must exist)

One password by uuid.

methodupdate(Minn\Http\Request $request, string $userId, string $uuid): Minn\Http\ResponsePOST /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/{uuid:[\w\-]+} (cap edit_user on {user_id}; user {user_id} must exist)PUT /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/{uuid:[\w\-]+} (cap edit_user on {user_id}; user {user_id} must exist)PATCH /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/{uuid:[\w\-]+} (cap edit_user on {user_id}; user {user_id} must exist)

Renames one.

methoddelete(Minn\Http\Request $request, string $userId, string $uuid): Minn\Http\ResponseDELETE /wp/v2/users/{user_id:(?:[\d]+|me)}/application-passwords/{uuid:[\w\-]+} (cap edit_user on {user_id}; user {user_id} must exist)

Removes one.

insidesubject(), existing(), validate(), item(), when()

ArgCheck final readonly class

public/minn/src/Minn/Rest/ArgCheck.php · 85 lines · Open the class →

Judges a route's declared arguments against the request before the policy is judged, the way the reference does: a required parameter that did not arrive is rest_missing_callback_param; the shared collection parameters (context, page, per_page, search) are judged first and a refusal among them is answered alone; then every other invalid one is listed in rest_invalid_param, in the order the route declares them, with the schema's own refusal under details. The query is read against the route's args, the JSON body against its body set, and a JSON body that does not parse is refused on every route first. An argument its handler judges (Args::HANDLER_VALIDATES) is left to the handler.

new__construct(Minn\Rest\Schema $schema)
methodclosure(): Closure

The check as the router takes it.

methodcheck(Minn\Http\Route $route, Minn\Http\Request $request, array $captures = array ( )): void

Throws the refusal the declared arguments earn, or returns. @param array<string, string> $captures what the path named, for a route whose parameters depend on it

@param array<string, string> $captures what the path named, for a route whose parameters depend on it
insidejson(), missing(), round()

BatchController final readonly class

public/minn/src/Minn/Rest/BatchController.php · 159 lines · Open the class →

batch/v1 as the reference answers it (probe rest-batch): up to 25 writes (POST, PUT, PATCH, DELETE) in one request, each answered in turn as {body, status, headers}, the whole as 207. Only routes that take part in batches may be asked: posts, pages and the other post types shown in REST (not media, templates, global styles or fonts), the taxonomies, users, widgets, and a plugin's routes that opt in with allow_batch. Any other route answers rest_batch_not_allowed with its Allow header, a path with no route rest_no_route. With require-all-validate, every request's arguments are judged first and one failure answers the whole batch with the failures alone (null for the rest), nothing written.

new__construct(Minn\Http\Router $router, Closure $dispatch, Minn\Rest\ArgCheck $args, Minn\Rest\Types $types)
@param Closure(Request): Response $dispatch answers one request as the REST API would on its own
methodbatch(Minn\Http\Request $request): Minn\Http\ResponsePOST /batch/v1 (public)

The requests answered in turn.

insiderefusal(), invalid(), claims(), batchablePath(), subRequest(), error(), envelope()

BatchRequest final class

public/minn/src/Minn/Rest/BatchRequest.php · 32 lines · Open the class →

The requests a batch payload names, normalised into descriptors the caller turns into real request objects. A path's query string is split off here so each sub-request carries its own query parameters, which is what lets one round trip stand in for several.

staticdescribe(mixed $requests): array

The batch body's requests in the reference's normalised form.

@return list<array{method: string, path: string, query: array<string, mixed>, body: ?array, headers: ?array}>

BlockRendererController final readonly class

public/minn/src/Minn/Rest/BlockRendererController.php · 89 lines · Open the class →

wp/v2/block-renderer as the reference answers it (probe rest-block-renderer), the route the editor's ServerSideRender asks: a dynamic block rendered from the attributes sent (query or body), checked against the block's own attributes and filled in with their defaults, inside the post named by post_id when there is one (it becomes the global post, and the block's postId and postType context). Only the edit context is accepted, and asking for none is asking for the view context, which is refused, as on the reference. Arguments are judged before the caller: someone who can edit posts may render, or edit the post named.

new__construct(Minn\Rest\Schema $schema, Minn\Rest\Caller $caller)
methodrender(Minn\Http\Request $request, string $name): Minn\Http\ResponseGET /wp/v2/block-renderer/{name:[a-z0-9-]+/[a-z0-9-]+} (public)POST /wp/v2/block-renderer/{name:[a-z0-9-]+/[a-z0-9-]+} (public)

The block's markup, as {"rendered": "..."}.

insiderefuseInvalid(), permit(), rendered()

BlockTypesController final readonly class

public/minn/src/Minn/Rest/BlockTypesController.php · 133 lines · Open the class →

wp/v2/block-types as the reference answers it (probe rest-block-types): every registered block type, core's and a plugin's, in registration order, or a namespace's share, for anyone who can edit a type shown in REST. Each carries its registration (attributes, supports, selectors, contexts, asset handles and the first of each as the old single field) with its styles (its own, then those registered for it) and its variations as built when asked; a dynamic block links to its renderer. Served with plugins loaded, since a plugin's blocks come from its code.

new__construct(Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/block-types (public)

Every block type, or those of one namespace (?namespace=).

methodinNamespace(Minn\Http\Request $request, string $namespace): Minn\Http\ResponseGET /wp/v2/block-types/{namespace:[a-zA-Z0-9_-]+} (public)

One namespace's block types.

methodsingle(Minn\Http\Request $request, string $namespace, string $name): Minn\Http\ResponseGET /wp/v2/block-types/{namespace:[a-zA-Z0-9_-]+}/{name:[a-zA-Z0-9_-]+} (public)

One block type.

staticfields(WP_Block_Type $type): array

A block type's fields in the reference's order, as the route and WP_REST_Block_Types_Controller show them: its registration (supports {} when it has none), its styles (its own, then those registered for it), its variations as built when asked, and the first handle of each asset kind as the old single field (probe rest-block-controllers).

@return array<string, mixed>
insideitems(), item(), links(), requireViewer()

BlocksController final readonly class

public/minn/src/Minn/Rest/BlocksController.php · 69 lines · Open the class →

wp/v2/blocks: synced patterns and reusable blocks, stored as wp_block posts. The reading and writing is the posts machinery with the post type's own capabilities; what is this type's alone is the reading gate: without edit_posts the list is empty and a single block is refused, published or not.

new__construct(Minn\Rest\PostsController $reads, Minn\Rest\PostsWriteController $writes, Minn\Content\Posts $posts, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/blocks (public)

The blocks the caller may edit; an empty list for anyone else.

methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/blocks/{id:[\d]+} (public; block {id} must exist)

One block. Editing context is the posts machinery's own gate (rest_forbidden_context); a view is refused to anyone who cannot edit posts, and a trashed or unknown block is not found rather than refused.

methodcreate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/blocks (cap publish_posts)

Creates a block: a pattern's create_posts is publish_posts.

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/blocks/{id:[\d]+} (cap edit_post on {id}; block {id} must exist)PUT /wp/v2/blocks/{id:[\d]+} (cap edit_post on {id}; block {id} must exist)PATCH /wp/v2/blocks/{id:[\d]+} (cap edit_post on {id}; block {id} must exist)

Updates a block.

methoddelete(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/blocks/{id:[\d]+} (cap delete_post on {id}; block {id} must exist)

Trashes or deletes a block.

Caller final class

public/minn/src/Minn/Rest/Caller.php · 149 lines · Open the class →

Who is making this REST call. Resolved once from the cookie and nonce; an anonymous or failed caller has id 0 and every capability check fails.

new__construct(Minn\Http\Request $request, Minn\Auth\Authenticator $authenticator, Minn\Auth\Capabilities $capabilities)
methodresolveAs(Minn\Auth\Authenticated $session): void

Settles the caller as a session already proven elsewhere: an in-process request a plugin makes through rest_do_request() carries no nonce, so it runs as whoever the outer request resolved.

methodresolveAnonymous(): void

Settles the caller as nobody: plugin code (determine_current_user) signed the request out.

methodcookieBound(): bool

Whether a sign-in cookie vouches for this caller (or failed to, for want of its nonce): a cookie's nonce vouches for its own user only.

methodrecheck(): void

Has the cookie and nonce checked again on the next question: a plugin's nonce lifetime (nonce_life) is heard only once plugins load.

methodresolveInvalidNonce(): void

Settles the caller as one whose cookie's nonce does not vouch for the user plugin code named.

methodsession(): ?Minn\Auth\Authenticated

The session, or null for an anonymous or refused caller.

methodid(): int

The caller's user id, 0 when anonymous.

methodcan(string $capability, ?int $postId = NULL): bool

Whether the caller holds a capability, on a post when given. With plugins loaded the answer is WordPress's: the same mapping, then map_meta_cap and user_has_cap, where role editors and lock plugins have their say.

methodeditsAnyRestType(): bool

Whether the caller can edit posts of some type shown in REST: what the reference asks before showing statuses, block types and the active theme. A plugin's types count once plugins are loaded.

methodcapabilities(): Minn\Auth\Capabilities

The capability engine.

methodrequire(string $code = 'rest_not_logged_in', string $message = 'You are not currently logged in.', int $status = 401): Minn\Auth\Authenticated

The session, or the reference's refusal: a bad nonce is always 403 rest_cookie_invalid_nonce; no identity is the caller-supplied error.

methodrequireFloor(string ...$capabilities): int

The floor every Minn Admin route shares: a signed-in caller who can edit posts, plus any further capability named, all refused with the same rest_forbidden the reference uses. Returns the caller's id.

methodrequireCap(string ...$capabilities): void

403 rest_forbidden unless the caller holds every capability named.

methodrefuse(string $code, string $message): Minn\RestError

401 for an anonymous caller, 403 for one who is signed in but refused.

insideresolve()

Catalogue final class

public/minn/src/Minn/Rest/Catalogue.php · 70 lines · Open the class →

The route table read from the classes alone: every #[Route] under src/Minn, as rows, with no request, database, or site behind it. This is what contracts/api/routes.json is written from, so an agent can read which routes exist, who may call them, and what they take, without booting the engine. The per-request router builds the same rows for the handlers it actually registered, which is what the live index serves.

staticscan(string $sourceDir): array

Every route declared by a class under the source directory, in file order, then declaration order.

@return list<RouteRow>
staticdocument(array $rows, string $engineVersion): array

The rows as the JSON catalogue: a version line and one entry per route.

@param list<RouteRow> $rows
@return array<string, mixed>
insideclasses()

ClientSchema final class

public/minn/src/Minn/Rest/ClientSchema.php · 81 lines · Open the class →

A JSON schema trimmed for a client (probe json-schema): only the keywords a profile allows, in the order given, at every level a sub-schema sits.

A property's boolean required becomes its object's required list (names whose required is true), kept where the object's own required is not a list, appended last otherwise, even where the profile drops required itself; an object's own list wins. A boolean required is always dropped. Sub-schemas are trimmed where they are objects: a keyword's value, a list's members, a map's entries; anything else stays as given.

statickeywords(string $profile): array

The keywords a profile keeps: draft-04's whole set, the REST API's for any other profile.

@return list<string>
statictrim(array $schema, array $allowed): array

A schema with only the allowed keywords, keyed by keyword (the values do not matter), trimmed all the way down.

@param array<array-key, mixed> $schema
@param array<array-key, mixed> $allowed
@return array<array-key, mixed>
insideposition(), member(), requiredNames()

CommentListArgs final class

public/minn/src/Minn/Rest/CommentListArgs.php · 34 lines · Open the class →

The WP_Comment_Query arguments a comment list request makes, as the reference makes them before rest_comment_query (probe rest-comment-lists): each declared parameter the request carries under its query name, an empty email and search when none is given, the orderby name mapped to the query's, the found rows counted, the posts loaded, the date bounds as one clause, the offset the page makes when none is given, and ids alone for a HEAD request.

staticof(WP_REST_Request $wp, array $registered, string $method): array

The arguments before plugins see them: a parameter maps when the collection (as rest_comment_collection_params left it) still has it.

@param array<string, mixed> $registered
@return array<string, mixed>

CommentObject final readonly class

public/minn/src/Minn/Rest/CommentObject.php · 87 lines · Open the class →

The wp/v2 comment object; edit context adds the moderation-desk fields.

CommentsController final readonly class

public/minn/src/Minn/Rest/CommentsController.php · 294 lines · Open the class →

wp/v2/comments: the status tabs with pagination headers, single, signed-in replies, moderation updates, trash, and force delete.

new__construct(Minn\Content\Comments $comments, Minn\Content\Posts $posts, Minn\Rest\CommentObject $object, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/comments (public)

The comments list as the reference serves it: the request's WP_Comment_Query through rest_comment_query, its totals (counted again when a page comes back empty), and the comments the caller may read.

methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/comments/{id:[\d]+} (public; comment {id} must exist)

One comment, if the caller may read it.

methodcreate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/comments (signed in)

A signed-in reply; the author fields come from the user.

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/comments/{id:[\d]+} (cap moderate_comments; comment {id} must exist)PUT /wp/v2/comments/{id:[\d]+} (cap moderate_comments; comment {id} must exist)PATCH /wp/v2/comments/{id:[\d]+} (cap moderate_comments; comment {id} must exist)

Status flips and content or author edits, for moderators.

methoddelete(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/comments/{id:[\d]+} (cap moderate_comments; comment {id} must exist)

Trash remembers where the comment came from; force removes it outright.

insidetotals(), events(), allowed(), readablePost(), readable(), plainComment(), prepared()

Context enum

public/minn/src/Minn/Rest/Context.php · 18 lines · Open the class →

The view a REST caller asked for. View is the public shape, edit adds the raw halves and the caller's own action links, embed is the reduced shape a linked resource carries when it rides inside another response.

Cases: View = 'view', Edit = 'edit', Embed = 'embed'

staticof(Minn\Http\Request $request): self

The context a request names, view when it names none or names one the route does not serve.

methodisEdit(): bool

Whether this is the edit context.

DeclaredPostsController final readonly class

public/minn/src/Minn/Rest/DeclaredPostsController.php · 56 lines · Open the class →

wp/v2/{rest_base} for extra post types declared by an active extension. Registered last so core collections (users, comments, menus, ...) match first; an unknown base is declined, so a plugin's own route under wp/v2 (answered by the runtime after the engine's routes) is not shadowed.

new__construct(Minn\Rest\Types $types, Minn\Rest\PostsController $reads, Minn\Rest\PostsWriteController $writes)
methodlist(Minn\Http\Request $request, string $base): Minn\Http\ResponseGET /wp/v2/{base:[a-z0-9_-]+} (declared type {base})

A declared type's list.

methodsingle(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseGET /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (declared type {base})

A declared type's single post.

methodcreate(Minn\Http\Request $request, string $base): Minn\Http\ResponsePOST /wp/v2/{base:[a-z0-9_-]+} (declared type {base})

Creates a post of a declared type.

methodupdate(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponsePOST /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (declared type {base})PUT /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (declared type {base})PATCH /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (declared type {base})

Updates a post of a declared type.

methoddelete(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseDELETE /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (declared type {base})

Trashes or deletes a post of a declared type.

insideslug()

DeclaredTermsController final readonly class

public/minn/src/Minn/Rest/DeclaredTermsController.php · 43 lines · Open the class →

wp/v2/{rest_base} for the taxonomies plugin code registers to show in REST (probe rest-plugin-types), answered as the core taxonomies are. Registered after the declared post types; a base no such taxonomy names declines, so a plugin's own route under wp/v2 is not shadowed.

new__construct(Minn\Rest\TermsController $terms)
methodlist(Minn\Http\Request $request, string $base): Minn\Http\ResponseGET /wp/v2/{base:[a-z0-9_-]+} (registered taxonomy {base})

A registered taxonomy's terms.

methodsingle(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseGET /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (registered taxonomy {base})

One term of a registered taxonomy.

methodcreate(Minn\Http\Request $request, string $base): Minn\Http\ResponsePOST /wp/v2/{base:[a-z0-9_-]+} (registered taxonomy {base})

Creates a term in a registered taxonomy.

methodupdate(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponsePOST /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (registered taxonomy {base})PUT /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (registered taxonomy {base})PATCH /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (registered taxonomy {base})

Updates a term of a registered taxonomy.

methoddelete(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseDELETE /wp/v2/{base:[a-z0-9_-]+}/{id:[\d]+} (registered taxonomy {base})

Deletes a term of a registered taxonomy.

Embed final class

public/minn/src/Minn/Rest/Embed.php · 219 lines · Open the class →

The _embed decoration and the embed context. Every embeddable link in an object's _links is answered in-process as the same caller with context=embed and placed under _embedded[rel], an error as the error itself; a rel whose every embed came back empty is left out, while an empty list beside a full one stays (wp:term keeps its empty tags list next to the categories). Lists have _fields applied per item before the links are read, so a _fields value without _links embeds nothing there; a single object embeds first and is filtered afterwards. The embed context is the view object cut to the reference's key set for that kind of item.

new__construct(Minn\Http\Router $router, Minn\Rest\Types $types, Minn\Rest\Taxonomies $taxonomies)
staticrequested(array $query): ?array

Null when _embed is absent; [] for every rel; otherwise the rels asked for. @return ?list<string>

@return ?list<string>
methoddecorate(Minn\Http\Request $request, Minn\Http\Response $response): Minn\Http\Response

Decorates a finished response: the embed context, then _embedded, then the deferred _fields.

methodembedded(Minn\Http\Request $request, array $item, array $rels): array

A response's data with its embeddable links answered (the runtime's WP_REST_Server::embed_links): every rel when none is named.

@param array<string, mixed> $item @param list<string> $rels
@return array<string, mixed>
staticshape(string $type, array $item): array

An item cut to the embed shape of its object type (a post type, a taxonomy, attachment, user or comment); any other kept whole. @param array<string, mixed> $item @return array<string, mixed>

@param array<string, mixed> $item @return array<string, mixed>
insidedecorateItem(), answer(), target(), kind(), taxonomyBase(), context()

EngineRoutes final class

public/minn/src/Minn/Rest/EngineRoutes.php · 66 lines · Open the class →

The engine's own REST routes in the reference's regex form, for the index and for the runtime's server, whose route table plugin code reads to learn what the site answers (a missing /wp/v2/comments there reads as "comments are off").

staticmap(Minn\Http\Router $router, array $declaredBases = array ( )): array

The router's routes as route => methods, the way the index lists them.

@param list<string> $declaredBases the rest_base of every declared type
@return array<string, list<string>> route => methods
staticforms(string $pattern, array $declaredBases = array ( )): array

A route attribute pattern as the reference writes routes: `{id:\d+}` becomes `(?P<id>\d+)`, a bare capture matches one segment, a `{rest*}` capture the remainder, and a literal constraint (`{base:posts}`, or the alternation `{base:posts|pages}`) expands to one route per literal. The declared-type routes are written once as a `{base:[...]}` catch-all; they list per declared type under its rest_base (as the reference lists a registered type) and not at all when no type is declared, so nothing listed answers no-route.

@param list<string> $declaredBases
@return list<string>

ExistingPost final class

public/minn/src/Minn/Rest/ExistingPost.php · 10 lines · implements Minn\Http\Refusal · Open the class →

A post created with an id names one that exists: refused with rest_post_exists before the caller is judged, even signed out (probe patch-security). An id of 0 passes here and is refused later, as an invalid post id.

staticrefuse(Minn\Http\Request $request): void

Throws rest_post_exists for a non-empty id in the body or the query.

Fields final readonly class

public/minn/src/Minn/Rest/Fields.php · 106 lines · Open the class →

The _fields response filter. Dot paths descend ("title.rendered"); the object's own key order is kept. Applied per item on list responses and to the whole payload otherwise, which is why _fields on the associative types response strips every key and yields [] over HTTP, a reference quirk the engine reproduces by construction.

propreadonly array $paths
propreadonly bool $deferred
staticfromQuery(array $query): ?self

The _fields selection a query carries, or null for everything.

@param array<string, mixed> $query
methodwithLinksForEmbedded(): self

The same paths plus _links whenever _embedded is among them: the reference's single-object rule under _embed.

methodwants(string $field): bool

Whether a top-level field is among the selection: named whole, below one of its paths (title.rendered), or above one (a path into it).

methodapply(array $object): array

The object with only the selected fields.

staticselect(array $available, array $requested): array

The fields a response keeps when a caller names some: known fields as given, dotted paths whose root is known, and `id` whenever it exists.

@param list<string> $available
@param list<string> $requested the parsed `_fields` list
@return list<string>
insidefilter()

GlobalStylesController final readonly class

public/minn/src/Minn/Rest/GlobalStylesController.php · 159 lines · Open the class →

wp/v2/global-styles: the site editor's saved styles (one post per theme, read and written by id, with its revisions) and the active theme's own styles and variations. There is no collection, no create and no delete; the post is made on first use, as on the reference.

new__construct(Minn\Theme\UserStyles $styles, Minn\Theme\ThemeStyles $theme, Minn\Rest\GlobalStylesObject $object, Minn\Content\Revisions $revisions, Minn\Rest\Caller $caller)
methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/global-styles/{id:[\/\d+]+} (public; global styles {id} must exist)

The saved styles by id: anyone who edits posts may read them, editing context needs the theme.

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/global-styles/{id:[\/\d+]+} (cap edit_theme_options; global styles {id} must exist)PUT /wp/v2/global-styles/{id:[\/\d+]+} (cap edit_theme_options; global styles {id} must exist)PATCH /wp/v2/global-styles/{id:[\/\d+]+} (cap edit_theme_options; global styles {id} must exist)

Replaces the title, settings, or styles the body names; what it leaves out stays.

methodvariations(Minn\Http\Request $request, string $stylesheet): Minn\Http\ResponseGET /wp/v2/global-styles/themes/{stylesheet:[\/\s%\w\.\(\)\[\]\@_\-]+}/variations (cap edit_posts)

The style variations the active theme ships; listed ahead of the theme's own route, whose pattern would take this path too.

methodtheme(Minn\Http\Request $request, string $stylesheet): Minn\Http\ResponseGET /wp/v2/global-styles/themes/{stylesheet:[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?} (cap edit_posts)

The active theme's settings and styles, the engine's defaults underneath.

methodrevisions(Minn\Http\Request $request, string $parent): Minn\Http\ResponseGET /wp/v2/global-styles/{parent:[\d]+}/revisions (cap edit_theme_options; global styles parent {parent} must exist)

The revisions of the saved styles, newest first; all of them unless per_page pages them.

methodrevision(Minn\Http\Request $request, string $parent, string $id): Minn\Http\ResponseGET /wp/v2/global-styles/{parent:[\d]+}/revisions/{id:[\d]+} (cap edit_theme_options; global styles parent {parent} must exist)

One revision of the saved styles.

insiderefuseIllegalCss(), post(), requireTheme(), requireParent(), title(), node()

GlobalStylesObject final readonly class

public/minn/src/Minn/Rest/GlobalStylesObject.php · 87 lines · Open the class →

The wp/v2/global-styles item, theme, and revision shapes.

new__construct(Minn\Content\Revisions $revisions, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methoditem(Minn\Content\PostRecord $post, Minn\Rest\Context $context): array

The saved styles: the title both ways, settings by origin, styles with their presets resolved, the links; in editing context the app's lock fields too, which a global-styles post never carries.

methodtheme(array $settings, array $styles, string $stylesheet): array

The active theme's own styles as the themes/{stylesheet} route answers them.

methodrevision(Minn\Content\PostRecord $revision): array

One revision row: both nodes when the body holds anything at all, neither when it is blank, then the row's own facts; no links.

insidelinks(), node()

IndexController final readonly class

public/minn/src/Minn/Rest/IndexController.php · 56 lines · Open the class →

The API index at /wp-json/: the site facts monitors read (name, url, home, namespaces) and the routes this engine serves, described from its own router rather than the reference's full schema; and the index of one namespace at /wp-json/{namespace}, the same routes narrowed.

new__construct(Minn\Content\Site $site, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url, Minn\Rest\RouteCatalogue $catalogue)
methodindex(Minn\Http\Request $request): Minn\Http\ResponseGET / (public)

The REST index: namespaces, routes, and the site's description.

methodnamespaceIndex(Minn\Http\Request $request, string $namespace): Minn\Http\ResponseGET /{namespace:[a-z0-9-]+/(?:v\d+|\d+\.\d+)} (public)

One namespace's index, as the reference answers /wp-json/wp/v2 (or oembed/1.0): the namespace, its routes (the namespace root among them), and the link up to the root index. A namespace the engine does not serve is left to the runtime, whose plugins may own it.

InstalledThemesController final readonly class

public/minn/src/Minn/Rest/InstalledThemesController.php · 141 lines · Open the class →

wp/v2/themes as the reference answers it (probe rest-themes): the installed themes, or only those with a status (active, inactive), each with its style.css headers raw and rendered, where it lives, and whether it is a block theme. The active theme also carries its supports as the editor reads them, the template types and part areas it may define, and a link to the theme export; any theme with user styles links to them. Anyone who can edit a type shown in REST may read the active theme; listing the others takes switch_themes. Each theme goes through rest_prepare_theme. Served with plugins loaded, since a theme's supports come from its own code.

new__construct(Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/themes (public)

The installed themes the caller may see.

methodsingle(Minn\Http\Request $request, string $stylesheet): Minn\Http\ResponseGET /wp/v2/themes/{stylesheet:[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?} (public)

One theme by its stylesheet.

methodfields(WP_Theme $theme, WP_REST_Request $request): array

A theme in the wp/v2 shape, its links included, before rest_prepare_theme (the runtime's WP_REST_Themes_Controller shapes through here); the request is what the supports' callbacks are handed.

@return array<string, mixed>
insideitem(), links(), viewsThemes(), viewsActive(), status(), requireRuntime()

LiveSettings final readonly class

public/minn/src/Minn/Rest/LiveSettings.php · 77 lines · Open the class →

wp/v2/settings with plugins loaded, served from the registered settings as the reference serves it (probe rest-settings): every setting shown in REST, core's and a plugin's alike, under its REST name. A read asks rest_pre_get_setting first, then get_option with the schema's default, and a value its schema refuses reads as null. A write takes the body's values its schema accepts (null always), offers each to rest_pre_update_setting, deletes the option for null (refused when what is stored does not fit the schema, since null could not restore it) and otherwise hands it to update_option.

new__construct(Minn\Rest\Schema $schema)
methodshown(): array

The settings shown in REST, by REST name, with the arguments the filters are handed.

@return array<string, array{name: string, schema: array<string, mixed>, option_name: string}>
methodpayload(): array

Every shown setting's value. @return array<string, mixed>

@return array<string, mixed>
methodstore(array $body): void

Writes the shown settings a body names, after refusing the values their schemas refuse.

insiderefuseInvalid()

MediaController final readonly class

public/minn/src/Minn/Rest/MediaController.php · 294 lines · Open the class →

wp/v2/media: list, single, upload on both transports (multipart field "file", or a raw body with Content-Disposition), field edits, and force delete with the files.

new__construct(Minn\Content\Posts $posts, Minn\Media\Writer $library, Minn\Rest\MediaObject $object, Minn\Rest\Caller $caller, Minn\Rest\PostsController $reads)
methodlist(Minn\Http\Request $request, string $base): Minn\Http\ResponseGET /wp/v2/{base:media} (public)

The media list: the post lists' own path for attachments (probe rest-media-lists), each item in the media shape.

methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/media/{id:[\d]+} (public; attachment {id} must exist; edit context: cap edit_post on {id})

One attachment.

methodcreate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/media (cap upload_files)

Uploads a file and creates its attachment.

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/media/{id:[\d]+} (cap edit_post on {id}; attachment {id} must exist)PUT /wp/v2/media/{id:[\d]+} (cap edit_post on {id}; attachment {id} must exist)PATCH /wp/v2/media/{id:[\d]+} (cap edit_post on {id}; attachment {id} must exist)

The editable fields the app uses.

methoddelete(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/media/{id:[\d]+} (cap delete_post on {id}; attachment {id} must exist)

Attachments go to the trash only when MEDIA_TRASH (or rest_attachment_trashable) says so; force removes the row, its meta, and its files.

insiderestDate(), storeWithPlugins(), insertedWithPlugins(), preparedAttachment(), finishedWithPlugins(), params(), attachment()

MediaObject final readonly class

public/minn/src/Minn/Rest/MediaObject.php · 171 lines · Open the class →

The wp/v2 media object, view and edit context.

new__construct(Minn\Content\Posts $posts, Minn\Media\Uploads $uploads, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller, Minn\Rest\Types $types)
methodbuild(Minn\Content\PostRecord $p, Minn\Rest\Context $context): array

An attachment in the context asked for, as rest_block_hooks_post_types has it (attachments only when a plugin lists them), through rest_prepare_attachment when a plugin hooks it.

methodurl(): Minn\Rest\RestUrl

The REST URL builder.

methodlinks(Minn\Content\PostRecord $p, Minn\Rest\Context $context): array

The attachment's links in a context: itself (with the methods the caller may use), its collection and type, its author, replies, the post it is attached to, and in the edit context the actions the caller may take; curies when any rel is the API's own.

@return array<string, list<array<string, mixed>>>
insidebuildFields(), details()

ParamCheck final class

public/minn/src/Minn/Rest/ParamCheck.php · 76 lines · Open the class →

The required / validate / sanitize pass over a request's declared arguments. The callbacks are plugin code and may answer with a WP_Error; the caller hands them in already normalised to true, false, or [message, details].

staticvalidate(array $args, callable $param, callable $validate): ?Minn\Runtime\Refusal

The first refusal among the request's parameters, or null when all pass.

@param array<string, array<string, mixed>> $args
@param callable(string): mixed $param the request's value for one argument
@param callable(string, mixed): (bool|array{0: string, 1: mixed}) $validate true, false, or [message, details]
staticsanitize(array $params, array $args, callable $sanitize): array

The parameters after sanitising, or the refusal listing every invalid one.

@param array<string, array<string, mixed>> $params the request's parameter groups, by source, in precedence order
@param array<string, array<string, mixed>> $args
@param callable(string, mixed): (array{value: mixed}|array{error: array{0: string, 1: mixed}}) $sanitize
@return array{0: array<string, array<string, mixed>>, 1: Refusal|null} the parameters after sanitising, and the refusal if any failed
insiderefusal()

PluginsController final readonly class

public/minn/src/Minn/Rest/PluginsController.php · 243 lines · Open the class →

wp/v2 plugins: what sits in wp-content/plugins, in the reference's shape. Minn extensions (a minn.json manifest) are listed alongside the WordPress plugins the folder still holds; the engine runs the former and only records the latter's stored state. Activation writes the same options the reference and the extension loader read; files are never installed or deleted from here.

new__construct(Minn\Content\Site $site, Minn\Content\Inventory $inventory, Minn\Extension\Loader $extensions, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller, Minn\Ops\Packages $packages)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/plugins (cap activate_plugins)

The plugins list, optionally by status.

methodinstall(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/plugins (cap activate_plugins)

Installs a directory plugin by slug, optionally activating it; answers 201 with the item.

methodsingle(Minn\Http\Request $request, string $plugin): Minn\Http\ResponseGET /wp/v2/plugins/{plugin:[^.\/]+(?:\/[^.\/]+)?} (cap activate_plugins)

One plugin.

methodupdate(Minn\Http\Request $request, string $plugin): Minn\Http\ResponsePUT /wp/v2/plugins/{plugin:[^.\/]+(?:\/[^.\/]+)?} (cap activate_plugins)POST /wp/v2/plugins/{plugin:[^.\/]+(?:\/[^.\/]+)?} (cap activate_plugins)PATCH /wp/v2/plugins/{plugin:[^.\/]+(?:\/[^.\/]+)?} (cap activate_plugins)

Activates or deactivates a plugin.

methoddelete(Minn\Http\Request $request, string $plugin): Minn\Http\ResponseDELETE /wp/v2/plugins/{plugin:[^.\/]+(?:\/[^.\/]+)?} (cap activate_plugins)

Deletes an inactive plugin.

staticshape(string $relative, string $path, array $headers, string $status): array

A WordPress plugin's item from its headers, as the reference's controller shapes one (the runtime's WP_REST_Plugins_Controller shapes through here too): the headers translated and cut to the allowed markup once for the fields and once more, marked up, for the rendered description.

@param array<string, mixed> $headers the plugin's headers, as get_plugins() holds them
@return array<string, mixed>
insideitems(), find(), statusOf(), manifestFor(), extensionItem(), pluginItem(), links(), extensionKey(), refuse()

PolicyGate final readonly class

public/minn/src/Minn/Rest/PolicyGate.php · 167 lines · Open the class →

Judges a route's policy against the caller, with the reference's refusals: a caller who is not signed in gets the policy's sign-in code at 401, a bad nonce is always 403 rest_cookie_invalid_nonce, and a signed-in caller who lacks a capability gets the refusal code at 403. A subject the policy names is looked up first and answers its 404 before any of those. The edit-context policy is judged as well when the request asks for it.

PostCollectionParams final class

public/minn/src/Minn/Rest/PostCollectionParams.php · 242 lines · implements Minn\Http\RouteParams · Open the class →

A post type's collection parameters as the reference builds them for its list (probe rest-post-lists): the shared four, dates, authors when the type supports them, ids, menu order and its orderby for page attributes, parents for a hierarchical type, search columns and semantics, slugs, statuses (every registered one, and any), each REST taxonomy's term filter (a list, or a query with children for a hierarchical one and an operator for the include side) under a relation, stickies for posts, and formats for a type with post formats. Plugins change them through rest_{type}_collection_params, asked once a request.

staticfor(array $captures): array

The parameters of the list a {base} capture names; none for a base no REST post type has.

staticofType(string $name): array

The parameters of a post type's list, by the type's name. @return array<string, array<string, mixed>>

@return array<string, array<string, mixed>>
staticlisted(string $name): array

A post type's list parameters asked afresh, as the reference's list asks for them each time it runs: rest_{type}_collection_params runs again.

@return array<string, array<string, mixed>>
staticbuilt(WP_Post_Type $object): array

A post type's list parameters as built, for the type in any namespace: before rest_{type}_collection_params and the optional marks, for the facade's controller class, which adds its callbacks and runs the filter itself.

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

The allowed MIME types by media type (the part before the slash), in the order the site allows them, each once.

@return array<string, list<string>>
insidenamed(), forType(), filtered(), type(), described(), build(), statuses(), taxonomies(), termFilter()

PostListArgs final class

public/minn/src/Minn/Rest/PostListArgs.php · 171 lines · Open the class →

The WP_Query arguments a post list request makes, as the reference makes them before rest_{type}_query (probe rest-post-lists): each registered parameter the request carries under its query name, the date bounds, per_page, stickies (only them, or none of them), an exact search, each taxonomy's include and exclude under the request's relation, formats (standard as no format at all), the type, and ids alone for a HEAD request. Then each through rest_query_var-{name}, the orderby names mapped to the query's, and stickies left where they fall unless asked.

staticof(WP_REST_Request $request, array $registered, string $type): array

The arguments before plugins see them.

@param array<string, mixed> $registered the list's parameters
@return array<string, mixed>
staticqueryVars(array $args, WP_REST_Request $request): array

The query variables from the arguments plugins left: each through rest_query_var-{name}, and the list's orderby names as the query's.

@param array<string, mixed> $args
@return array<string, mixed>
insidemimeTypes(), sticky(), taxonomies(), termClause(), formats()

PostObject final readonly class

public/minn/src/Minn/Rest/PostObject.php · 533 lines · Open the class →

Builds the wp/v2 post and page objects in the reference's shape: the view context, the edit context (raw+rendered dual fields, editor-only fields, and cap-gated wp:action-* links), and their _links blocks.

new__construct(Minn\Db $db, Minn\Content\Posts $posts, Minn\Content\Users $users, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodview(Minn\Content\PostRecord $p, ?Minn\Rest\Fields $fields = NULL): array

The view-context object, through rest_prepare_{type} when a plugin hooks it; with a _fields selection, the rendered title, content and excerpt only when it asks for them, as the reference renders only what is asked.

methodedit(Minn\Content\PostRecord $p, int $userId, ?Minn\Rest\Fields $fields = NULL): array

The edit-context object, its raw content given its hooked blocks, through rest_prepare_{type} when a plugin hooks it; with a _fields selection, rendered only as far as it asks.

methodpermalink(Minn\Content\PostRecord $p): string

A post's permalink as of now: pretty once live with a slug, ?p= (or ?page_id=) before.

staticrestBase(string $type): string

The rest_base of a type.

methodlinks(Minn\Content\PostRecord $p): array

The _links of a post in the view context.

methodmodifiedUnsaved(Minn\Content\PostRecord $p, int $userId): bool

Whether a live post carries an autosave newer than its saved revision.

methodlockHolder(int $id, int $userId): ?array

The OTHER user holding a live _edit_lock (150 second window), or null.

methodeditLinks(Minn\Content\PostRecord $p, int $userId, ?array $viewLinks = NULL): array

The view links plus the caller's verbs and cap-gated wp:action-* entries.

staticdate(string $mysql): string

A MySQL datetime in the reference's ISO form.

insidemeta(), navigationView(), blockView(), viewFields(), viewTerms(), typeFields(), classList(), format(), termLinks(), editFields(), allow(), gmt()

PostsController final readonly class

public/minn/src/Minn/Rest/PostsController.php · 197 lines · Open the class →

wp/v2 posts and pages, read side.

new__construct(Minn\Db $db, Minn\Content\Posts $posts, Minn\Rest\PostObject $object, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request, string $base): Minn\Http\ResponseGET /wp/v2/{base:posts} (public)GET /wp/v2/{base:pages} (public)

The posts or pages list.

methodserveList(Minn\Http\Request $request, string $type, ?Closure $shape = NULL): Minn\Http\Response

The list for any post type as the reference serves it (probe rest-post-lists): the caller and the statuses judged, the request's parameters sanitized, the query arguments through rest_{type}_query and rest_query_var-*, a WP_Query (so pre_get_posts and every query filter run), then each post the caller may read (or edit, in the edit context) shaped (by $shape when the type has its own object, as media does). The totals are the query's, counted again without the page when a later page came back empty. A media search also matches file names, as the reference's does.

@param (\Closure(PostRecord, Context): array<string, mixed>)|null $shape
methodsingle(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseGET /wp/v2/{base:posts|pages}/{id:[\d]+} (public; post {id} must exist; edit context: cap edit_post on {id})

One post or page.

methodserveSingle(Minn\Http\Request $request, string $type, string $id): Minn\Http\Response

One post of any type, with the reference's read rules.

staticwithAlternate(Minn\Http\Response $response, Minn\Content\PostRecord $post): Minn\Http\Response

A viewable type's single post points at its page on the site.

insidetotals(), readable(), visibleStatuses()

PostsWriteController final readonly class

public/minn/src/Minn/Rest/PostsWriteController.php · 518 lines · Open the class →

wp/v2 posts and pages, write side: create, update, trash, and force delete, each gated by the capability engine and answering with the edit-context object the reference returns.

new__construct(Minn\Content\Posts $posts, Minn\Content\PostWriter $writer, Minn\Content\Site $site, Minn\Rest\PostObject $object, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodcreate(Minn\Http\Request $request, string $base): Minn\Http\ResponsePOST /wp/v2/{base:posts} (cap edit_posts)POST /wp/v2/{base:pages} (cap edit_pages)

Creates a post or page; one asked for with an id is refused before the caller is judged (patch-security).

methodserveCreate(Minn\Http\Request $request, string $type, string $base): Minn\Http\Response

Creates a post of any type from the body.

methodupdate(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponsePOST /wp/v2/{base:posts|pages}/{id:[\d]+} (cap edit_post on {id}; post {id} must exist)PUT /wp/v2/{base:posts|pages}/{id:[\d]+} (cap edit_post on {id}; post {id} must exist)PATCH /wp/v2/{base:posts|pages}/{id:[\d]+} (cap edit_post on {id}; post {id} must exist)

Updates a post or page.

methodserveUpdate(Minn\Http\Request $request, string $type, string $id): Minn\Http\Response

Updates a post of any type from the body.

methoddelete(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseDELETE /wp/v2/{base:posts|pages}/{id:[\d]+} (cap delete_post on {id}; post {id} must exist)

Trashes or deletes a post or page.

methodserveDelete(Minn\Http\Request $request, string $type, string $id): Minn\Http\Response

Trashes a post of any type, or deletes it with force.

staticcheckMeta(array $body): void

A body's meta must be an object of keys (probe rest-meta). @param array<string, mixed> $body

@param array<string, mixed> $body
staticfield(mixed $value): string

A field that may arrive as a scalar or as {raw: ...}.

insidehasParent(), hasOrder(), events(), newColumns(), writeNewPost(), checkAssignTerms(), rememberOld(), floatingDate(), scheduledIfFuture(), askedDate(), updatedDate(), fieldColumns(), statusColumns(), checkStickyChange(), checkStickyPasswordConflict(), validStatus(), clean()

RegisteredFields final class

public/minn/src/Minn/Rest/RegisteredFields.php · 105 lines · Open the class →

The fields plugin code adds to an object type with register_rest_field, as the reference serves them (probe rest-fields). A get callback's value follows the item's own fields, before its links. It is handed the item so far (without links, cut to the fields asked for, id kept), the field's name, the request and the object type. A field is left out when _fields does not name it or its schema's context does not include the request's. A write runs the update callbacks for the fields its body names, with the saved object, before rest_after_insert; an error one returns is the write's answer.

staticadd(array $item, string $type, WP_REST_Request $request): array

The item with the type's registered fields added, answering the request. @param array<string, mixed> $item @return array<string, mixed>

@param array<string, mixed> $item @return array<string, mixed>
staticupdate(object $object, string $type, WP_REST_Request $request): void

Runs the update callbacks for the fields the request's body names; an error one returns is thrown as the write's answer.

staticcontext(WP_REST_Request $request, string $type): string

The context the item is answered in, set on the request as the reference's controllers set it: a write and a delete answer in edit (a term's delete in view); a read in the one asked for, or view.

staticshownIn(string $type, string $context): array

The names of the fields registered for the type that show in the context. @return list<string>

@return list<string>
insideof(), wanted(), inContext(), refusal()

RegisteredPostFields final readonly class

public/minn/src/Minn/Rest/RegisteredPostFields.php · 89 lines · Open the class →

The REST object of a post whose type plugin code registered (probe rest-plugin-types), built by what the type supports.

new__construct(Minn\Content\Posts $posts, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url)
methodview(Minn\Content\PostRecord $p, Minn\Rest\RegisteredType $type, Closure $allow, Closure $gmt): array

A plugin's type in the view context (probe rest-plugin-types): the shared fields, then only what the type supports (title, editor, excerpt, author, thumbnail, page attributes, comments, formats, custom fields), its parent when hierarchical, each REST taxonomy's term ids under its REST base, the class list and links.

@param Closure(int): list<string> $allow the methods the caller may use on a post
@param Closure(string, string): string $gmt a GMT date as the object writes it
@return array<string, mixed>
insidelinks()

RegisteredType final readonly class

public/minn/src/Minn/Rest/RegisteredType.php · 46 lines · Open the class →

A post type plugin code registered, as its REST object follows it (probe rest-plugin-types): what it supports, whether it is hierarchical, and the taxonomies it shows in REST under their REST bases. Only once the runtime has loaded the plugins that register it.

propreadonly string $name
staticof(string $type): ?self

The type when plugin code registered it (not a built-in), or null.

methodsupports(string $feature): bool

Whether the type supports the feature (title, editor, author...).

methodhierarchical(): bool

Whether the type's posts have parents.

methodbase(): string

The REST base the type's routes live under, with its namespace.

methodtaxonomies(): array

The taxonomies the type shows in REST, REST base => taxonomy, in registration order. @return array<string, string>

@return array<string, string>

RenderedFields final class

public/minn/src/Minn/Rest/RenderedFields.php · 68 lines · Open the class →

A post's rendered title, content and excerpt as a REST response carries them. Without plugins they are Minn's own render. With plugins loaded they pass the filters the reference's controller runs, with the post set up as it sets it up: the_title over the stored title, the engine's render of the content then the rest of the_content (shortcodes and every plugin's callback), and get_the_excerpt then the_excerpt.

statictitle(Minn\Content\PostRecord $p): string

The rendered title.

staticcontent(Minn\Content\PostRecord $p): string

The rendered content.

staticexcerpt(Minn\Content\PostRecord $p): string

The rendered excerpt.

staticsetUp(Minn\Content\PostRecord $p): void

The post made current and set up for a response that renders none of its text, as the reference sets up every item it prepares.

staticclasses(array $classes, int $postId): array

A post's class list as get_post_class hands it back once plugin code filters post_class (WooCommerce drops hentry and adds a product's stock and type): the filter's answer without repeats, in order.

@param list<string> $classes
@return list<string>
insidewithPost()

Reply final class

public/minn/src/Minn/Rest/Reply.php · 53 lines · Open the class →

JSON responses in the reference's shape: its header set, its json_encode flags (slashes escaped), the _fields filter, and the pagination headers on lists.

constHEADERS = array ( 'Content-Type' => 'application/json; charset=UTF-8', 'X-Content-Type-Options' => 'nosniff', 'Access-Control-Expose-Headers' => 'X-WP-Total, X-WP-TotalPages, Link', 'Access-Control-Allow-Headers' => 'Authorization, X-WP-Nonce, Content-Disposition, Content-MD5, Content-Type', )
staticanswer(Minn\Http\Request $request, mixed $data, int $status = 200): Minn\Http\Response

One item, shaped by the request's own _fields: what nearly every handler ends with.

staticitem(mixed $data, ?Minn\Rest\Fields $fields, int $status = 200): Minn\Http\Response

One object as a response, the selected fields applied.

staticalternate(Minn\Http\Response $response, string $permalink): Minn\Http\Response

A post's answer pointing at its page on the site, as a viewable type's single item does.

staticlist(array $rows, int $total, int $totalPages, ?Minn\Rest\Fields $fields): Minn\Http\Response

A list response with the total and page-count headers.

@param list<array> $rows
staticerror(Minn\RestError $error): Minn\Http\Response

A REST error as the reference's error body.

RestMeta final class

public/minn/src/Minn/Rest/RestMeta.php · 265 lines · Open the class →

An object's meta field over REST, from the keys registered to show there (probe rest-meta). Each key answers under its REST name (its own, or show_in_rest's name): those for every subtype, then the object's subtype's, left out of a context their schema does not name. A single key reads its first stored value (its default, or its type's empty value, when none), a multiple key each one; each value is checked against the key's schema (null when it does not fit, the empty value of a scalar type for '') and cast, or handed to the key's prepare callback. A write goes key by key in registration order: null (or an empty list) deletes, a value its schema refuses is a 400, and a key the user may not edit is a 403 (an unchanged single value is left alone first). The errors are gathered; the first is the answer, with the others beside it.

staticread(string $objectType, int $objectId, string $subtype, ?string $context = NULL): array

The meta field of an object in a context (the one the request answers in, when none is named), keyed by REST name. @return array<string, mixed>

@return array<string, mixed>
staticvalue(string $objectType, int $objectId, string $key, array $field, WP_REST_Request $request): mixed

One field's value for an object: a single key's first stored value (its default when none), a multiple key's each one, prepared for the response.

@param array<string, mixed> $field
staticwriteFrom(WP_REST_Request $request, string $objectType, int $objectId, string $subtype): void

Applies the meta a write request names, when it names any.

staticwrite(string $objectType, int $objectId, string $subtype, array $meta): void

Applies a request's meta to an object; the gathered refusals are thrown.

staticerrors(string $objectType, int $objectId, string $subtype, array $meta): array

Applies a request's meta to an object, key by key in registration order; the refusals come back. @return list<\WP_Error>

@return list<\WP_Error>
staticfields(string $objectType, string $subtype): array

The keys registered to show in REST for an object type and subtype, each with its REST name, whether it is single, its schema (a multiple key's an array of its own) and its prepare callback; keys of a type REST does not know are left out.

@return array<string, array{name: string, single: bool, schema: array<string, mixed>, prepare_callback: mixed}>
staticprepare(mixed $value, array $field, WP_REST_Request $request): mixed

A value for the response: through the field's prepare callback, or checked against its schema (null when it does not fit) and cast. @param array<string, mixed> $field

@param array<string, mixed> $field
staticstore(string $objectType, int $objectId, string $subtype, string $key, array $field, mixed $value): ?WP_Error

A single key stored: an unchanged value is left alone, then the user must be allowed to edit it. @param array<string, mixed> $field

@param array<string, mixed> $field
staticstoreAll(string $objectType, int $objectId, string $subtype, string $key, array $field, array $values): ?WP_Error

A multiple key stored as a list, once the user may edit it. @param array<string, mixed> $field @param array<mixed> $values

@param array<string, mixed> $field @param array<mixed> $values
staticdelete(string $objectType, int $objectId, string $key, array $field): ?WP_Error

A key deleted once the user may delete it (a single key's invalid stored value refuses null). @param array<string, mixed> $field

@param array<string, mixed> $field
staticsame(string $objectType, string $subtype, string $key, array $field, mixed $stored, mixed $value): bool

Whether a value sanitizes to what is stored (scalar types compared as the text the store keeps). @param array<string, mixed> $field

@param array<string, mixed> $field
staticemptyValue(string $type): mixed

A type's empty value: '' for a string, false, 0, or an empty list.

insideupdate(), replaceAll(), refused(), failed(), nullStored(), gathered(), request()

RestUrl final readonly class

public/minn/src/Minn/Rest/RestUrl.php · 38 lines · Open the class →

REST URLs in the form the reference emits for the site's permalink mode: {home}/wp-json/wp/v2/... when pretty, otherwise {home}/index.php?rest_route=/wp/v2/... with the route value URL-encoded when query args ride along.

new__construct(Minn\Front\Permalinks $permalinks)
methodhome(string $path = ''): string

A URL under the site's home.

methodto(string $route, array $args = array ( )): string

The URL of a REST route, pretty or plain as the site is set.

@param array<string, string|int> $args
staticcuries(): array

The curies entry every wp/v2 _links block ends with.

RevisionsController final readonly class

public/minn/src/Minn/Rest/RevisionsController.php · 237 lines · Open the class →

wp/v2 revisions under posts, pages, patterns and navigation menus, and autosaves under those and menu items (probes rest-revisions, rest-autosaves). A revision carries the fields its parent's type supports: a navigation menu's its title and content, a menu item's its title alone.

new__construct(Minn\Content\Posts $posts, Minn\Content\Revisions $revisions, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller, ?Minn\Front\Permalinks $permalinks = NULL)
methodrevisions(Minn\Http\Request $request, string $base, string $parent): Minn\Http\ResponseGET /wp/v2/{base:posts|pages|blocks|navigation}/{parent:[\d]+}/revisions (cap edit_post on {parent}; post parent {parent} must exist)

Real revisions, not autosaves.

methodrevision(Minn\Http\Request $request, string $base, string $parent, string $id): Minn\Http\ResponseGET /wp/v2/{base:posts|pages|blocks|navigation}/{parent:[\d]+}/revisions/{id:[\d]+} (cap edit_post on {parent}; post parent {parent} must exist)

One revision of a post, page, or block.

methoddeleteRevision(Minn\Http\Request $request, string $base, string $parent, string $id): Minn\Http\ResponseDELETE /wp/v2/{base:posts|pages|blocks|navigation}/{parent:[\d]+}/revisions/{id:[\d]+} (cap delete_post on {parent}; post parent {parent} must exist)

A revision deleted for good, answered as it was; never trashed. The reference grants deleting a revision to nobody by default, so a request that passes the parent's check is refused at the revision's.

methodautosaves(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseGET /wp/v2/{base:posts|pages|blocks|navigation|menu-items}/{id:[\d]+}/autosaves (cap edit_post on {id}; post parent {id} must exist)

The autosaves of a post.

methodautosave(Minn\Http\Request $request, string $base, string $parent, string $id): Minn\Http\ResponseGET /wp/v2/{base:posts|pages|blocks|navigation|menu-items}/{parent:[\d]+}/autosaves/{id:[\d]+} (cap edit_post on {parent}; post parent {parent} must exist)

The caller's autosave of the parent, whichever autosave id is asked for; the edit view names its preview.

methodcreateAutosave(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponsePOST /wp/v2/{base:posts|pages|blocks|navigation|menu-items}/{id:[\d]+}/autosaves (cap edit_post on {id}; post {id} must exist)

One autosave slot per author; the reply carries a preview link.

methodobject(Minn\Content\PostRecord|array $r, Minn\Rest\Context $context = Minn\Rest\Context::View): array

One revision row as wp/v2 serves it (autosaves and revisions alike).

insideclean(), withPreviewLink(), previewLink(), meta(), requireParent(), parentOf()

RouteArgs final class

public/minn/src/Minn/Rest/RouteArgs.php · 34 lines · Open the class →

The argument groups a route registers with, filled the way register_rest_route fills them.

staticnormalise(array $args): array

Route arguments with the shared args folded into each endpoint.

@param array<string, mixed> $args a single handler (with 'callback') or a list of handler groups, plus optional shared 'args'
@return array{0: array<int|string, mixed>, 1: bool} the groups, and whether any lacks a permission_callback

RouteCatalogue final readonly class

public/minn/src/Minn/Rest/RouteCatalogue.php · 78 lines · Open the class →

The routes the engine serves, described in the reference's shape for the index and for an OPTIONS request: every route's namespace, its methods and the parameters it declares, gathered per concrete route from the attributes so a client learns what really works on it, with the namespaces they fall under. Routes are keyed as the reference spells them ('/wp/v2/posts/(?P<id>[\d]+)').

new__construct(Minn\Http\Router $router, Minn\Rest\Types $types, Minn\Rest\RestUrl $url)
methodall(): array

Every route the engine serves, the root and each namespace's index among them, and the namespaces.

@return array{namespaces: list<string>, routes: array<string, array<string, mixed>>}
staticnamespaceOf(string $route): string

The namespace a route is listed under: its first two segments when the second is a version. The routes the reference's server registers itself, the batch route among them, belong to none, so none is listed for them and their path answers no namespace index.

methoddescribing(string $path): ?array

The first route whose pattern takes the path, as the reference finds the route an OPTIONS request describes; null when none does.

@return array<string, mixed>|null

RouteIndex final class

public/minn/src/Minn/Rest/RouteIndex.php · 61 lines · Open the class →

The description of one route the REST index publishes: namespace, methods, endpoints with their argument schemas, self link.

staticdescribe(string $route, array $callbacks, array $options, string $context, callable $restUrl): ?array

One route as the index describes it, or null when hidden.

@param list<array<string, mixed>> $callbacks the route's handler groups
@param array<string, mixed> $options the route's non-numeric options (namespace, schema)
@param callable(string): string $restUrl
@return array<string, mixed>|null null when no endpoint shows in the index
insideargument()

RouteMatch final class

public/minn/src/Minn/Rest/RouteMatch.php · 68 lines · Open the class →

Finds the registered handler for a method and path among the runtime's route table.

staticfind(array $namespaces, callable $routesFor, string $method, string $path): Minn\Runtime\Refusal|array

The handler for a method and path across the registered namespaces, or the refusal.

@param list<string> $namespaces the registered namespaces
@param callable(string): array<string, list<array<string, mixed>>> $routesFor the (filtered) routes of one namespace, or all when given ''
@return array{route: string, handler: array<string, mixed>, params: array<string, string>, defaults: array<string, mixed>}|Refusal
staticroute(array $namespaces, callable $routesFor, string $path): ?array

The first registered route whose pattern matches a path, whatever the method, with its handlers; null when none does. An OPTIONS request is answered from this.

@param list<string> $namespaces
@param callable(string): array<string, list<array<string, mixed>>> $routesFor
@return array{route: string, handlers: list<array<string, mixed>>}|null

RouteTable final class

public/minn/src/Minn/Rest/RouteTable.php · 38 lines · Open the class →

The registered endpoints in dispatch shape: one handler list per route, methods as a set, non-numeric keys lifted into the route's options.

staticnormalise(array $endpoints): array

Registered endpoints as a route table with their options.

@param array<string, mixed> $endpoints route => a handler or a list of handlers plus options
@return array{0: array<string, list<array<string, mixed>>>, 1: array<string, array<string, mixed>>} the routes, and the options found per route

RuntimeEnvelope final readonly class

public/minn/src/Minn/Rest/RuntimeEnvelope.php · 123 lines · implements Minn\Http\Envelope · Open the class →

The REST server's filters around one of Minn's own routes, as the reference runs them around every route (contracts/runtime.md, "The REST server's envelope"): rest_request_before_callbacks is handed the argument check's error or null and may refuse; the route's permission is the next refusal; rest_dispatch_request may answer in place of the route; rest_request_after_callbacks sees whatever came of it and may change it. A value other than an error from the first filter does not answer: the route still runs. With nothing hooked on any of the three, the route answers as it would without plugins, unconverted; and before the runtime boots (the API is built first) there are no plugins to ask.

RuntimePrepare final class

public/minn/src/Minn/Rest/RuntimePrepare.php · 145 lines · Open the class →

An item a REST response carries, through the filter the reference runs as it prepares one (rest_prepare_{type}, rest_prepare_attachment, rest_prepare_user, rest_prepare_comment, rest_prepare_{taxonomy}): the item as a response object, its links on the response rather than in its data, handed with the object it describes and the request; what the filter leaves is the item. With nothing hooked, or a response handed back untouched, the item is Minn's own, byte for byte. Fields plugin code registers for the type are added first, as the reference adds them before the filter runs.

constBLOCK_HOOKS_TYPES = array ( 0 => 'post', 1 => 'page', 2 => 'wp_block', 3 => 'wp_navigation', )

The post types whose REST answers and saves get their hooked blocks, before plugins say.

staticanswering(WP_REST_Request $request): void

Remembers the request a REST call is answering, for the filters its items pass through (on the request's own runtime).

staticduring(WP_REST_Request $request, Closure $run): mixed

Runs an in-process REST call answering its own request (the filters hand plugins that one), then puts back the request it interrupted.

@param \Closure(): T $run
staticpostItem(string $filter, array $item, int $postId, string $type): array

A post-shaped item (a post, a page, a pattern, a navigation, an attachment, any other type) through rest_prepare_{type}, given its hooked blocks first; the post it describes is looked up only when a hook needs it.

@param array<string, mixed> $item
@return array<string, mixed>
staticsavedWithHookedBlocks(stdClass $prepared, string $type): void

A prepared post about to be saved, as rest_block_hooks_post_types has it: a type it lists has its content's ignored hooked blocks marked, a navigation's root list put in meta_input, as the reference's update_ignored_hooked_blocks_postmeta does; asked of every save.

staticitem(string $filter, array $item, Closure $described, ?string $postType = NULL): array

One item through a rest_prepare filter.

@param array<string, mixed> $item
@param \Closure(): mixed $described the object the item describes, fetched only when a plugin listens
@return array<string, mixed>
insidehookedBlocks(), withHookedBlocks(), objectType()

RuntimeRoutes final class

public/minn/src/Minn/Rest/RuntimeRoutes.php · 479 lines · Open the class →

Routes plugin code registered with register_rest_route(), answered through the runtime's server after the engine's own routes have had their turn; the runtime's say before the engine answers at all (an authentication refusal, a pre-dispatch answer, a removed endpoint); and the runtime's namespaces folded into the index.

staticgate(Minn\Http\Request $request): ?Minn\Http\Response

What plugin code decides before any route runs, engine routes included, in the reference's order: rest_authentication_errors may refuse the request, rest_pre_dispatch may answer it outright, and a route a rest_endpoints filter removed is no route at all. Null lets the engine's router proceed.

staticpredispatch(Minn\Http\Request $request): ?Minn\Http\Response

What plugin code decides before a route runs, for a request inside another (an embedded link) as for the first: rest_pre_dispatch may answer it outright, and a route a rest_endpoints filter removed is no route at all. Null lets the router proceed.

staticdispatch(Minn\Http\Request $request): ?Minn\Http\Response

Null when the runtime has no route for the request either.

staticallowedMethods(string $route, WP_REST_Request $wpRequest): array

The methods of a plugin's route whose handler lets this request through (a handler with no permission callback does). @return list<string>

@return list<string>
statictargetHints(string $href): ?array

The methods a self link's caller may use there, as the reference hints them on every self link it serves (targetHints.allow): the route the link names, each of its methods whose handler lets this caller through; null when no route of the runtime's takes the link. A route the engine answers itself is judged by its policies, as its Allow header is (probe rest-registry-controllers).

@return list<string>|null
staticmergeIndex(Minn\Http\Response $response): Minn\Http\Response

The engine's index plus the namespaces and routes the runtime holds.

staticadopt(Minn\Http\Request $request, WP_REST_Request $wpRequest): void

An in-process call's own request object stands for the engine's request: the server sets its route's parameters on it, as the reference's dispatch does.

staticsanitized(Minn\Http\Request $request, array $registered): WP_REST_Request

The request as the list's handler reads it: its parameters with their defaults, sanitized by their schemas, as plugins' filters see it too.

@param array<string, array<string, mixed>> $registered
staticwpRequest(Minn\Http\Request $request): WP_REST_Request

The request as the runtime's server reads it, and as a REST filter or action hands it to plugins: the same object each time.

staticmatched(Minn\Http\Request $request, string $route, array $handler): void

Keeps the route and handler a request matched, for the response plugins see at serving.

@param array<string, mixed> $handler
statictoWpError(Minn\RestError $error): WP_Error

An engine refusal as plugins handle one.

statictoWp(Minn\Http\Response $response): WP_REST_Response

An engine answer as plugins handle one: its data decoded (an empty object stays one), its links on the response rather than in the data, its status and headers.

staticwpHeaders(array $headers, string $route = ''): array

A route's own headers as the reference's response object holds them: a list's totals are numbers, but the comments list's stay text.

@param array<string, string> $headers
@return array<string, string|int>
staticitemResponse(array $item): WP_REST_Response

One item a response carries, as a response object: its data without _links, the links on the response.

@param array<string, mixed> $item
staticfromWp(mixed $result): Minn\Http\Response

Whatever the filters left, as the engine sends it; what they handed back untouched is the engine's own answer, byte for byte.

staticserve(Minn\Http\Request $request, Minn\Http\Response $response): Minn\Http\Response

The server's last word on any REST answer, engine or plugin route, as the reference serves one: rest_post_dispatch may change it, rest_pre_serve_request may serve it itself (what it prints is the body), and rest_pre_echo_response may rewrite the data echoed. With nothing hooked, the answer goes out as it is.

staticservedAsXml(Minn\Http\Request $request, Minn\Http\Response $response): bool

Whether the answer went out as oEmbed's XML. The reference prints it in rest_pre_serve_request ahead of rest_send_cors_headers, so none of the CORS headers follow it, Vary: Origin among them.

staticensure(mixed $result): WP_REST_Response

A callback's return as a response object, an error converted.

staticserver(): WP_REST_Server

The runtime's REST server, the one door to it (built, with rest_api_init, on first use).

insideallow(), dispatchDone(), asksForXml(), oembedXml(), look(), remember(), decode(), expand(), newWpRequest(), toResponse()

Schema final readonly class

public/minn/src/Minn/Rest/Schema.php · 503 lines · Open the class →

JSON-schema handling the way the REST API's argument validation does it: validate a value against a schema (a Refusal names what failed), sanitise it into the schema's type, filter a response by context, and the type tests the schema vocabulary needs. Behaviour pinned by contracts/fixtures/api/rest.json.

constKEYWORDS = array ( 0 => 'title', 1 => 'description', 2 => 'default', 3 => 'type', 4 => 'format', 5 => 'enum', 6 => 'items', 7 => 'properties', 8 => 'additionalProperties', 9 => 'patternProperties', 10 => 'minProperties', 11 => 'maxProperties', 12 => 'minimum', 13 => 'maximum', 14 => 'exclusiveMinimum', 15 => 'exclusiveMaximum', 16 => 'multipleOf', 17 => 'minLength', 18 => 'maxLength', 19 => 'pattern', 20 => 'minItems', 21 => 'maxItems', 22 => 'uniqueItems', 23 => 'anyOf', 24 => 'oneOf', )

Every keyword a route schema may carry, in the reference's order; the endpoint subset drops the three descriptive ones.

new__construct(Closure $email, Closure $number, Closure $format)
@param Closure(string): bool $email the email test, so the reference's is_email filter applies
@param Closure(int|float): string $number the localised number formatter for messages
@param Closure(string, mixed): mixed $format the sanitiser for a string format (hex-color, email, uri, ...)
methodvalidate(mixed $value, array $args, string $param = ''): Minn\Runtime\Refusal|true

Whether a value satisfies a schema, or the reference's refusal.

methodchoices(): Minn\Rest\SchemaChoices

A schema's anyOf and oneOf: which of their schemas a value matches, and the refusal when not as wanted.

methodvalidateAs(string $type, mixed $value, array $args, string $param): Minn\Runtime\Refusal|true

One type's own checks, as the per-type validators run them: a number's bounds before an integer's wholeness, and no enum or format.

methodvalidateEnum(mixed $value, array $args, string $param): Minn\Runtime\Refusal|true

Whether a value is one of the schema's enum, coerced to the schema's type first when it names one.

methodsanitize(mixed $value, array $args, string $param = ''): mixed

A value coerced to its schema's types.

methodfilterByContext(mixed $data, array $schema, string $context): mixed

Drops the properties whose schema does not list the context; an array whose items do not is emptied.

methodanyMatching(mixed $value, array $args, string $param): ?array

The first anyOf branch the value validates against.

staticendpointArgs(array $schema, bool $creatable): array

The argument map a route derives from an item schema: every writable property with the default validators, its keywords, defaults and required flags on the create route only, and any arg_options overrides.

staticcastEmptyObjects(mixed $value, array $schema): mixed

A value with each empty array the schema calls an object made one (it encodes as {}), through properties, additional properties, list items and the value as a whole (probe rest-view-config).

@param array<string, mixed> $schema
staticcloseObjects(array $schema): array

The schema with additionalProperties closed on every object.

insidevalidateInteger(), validateString(), validateNumber(), validateArray(), firstMissing(), validateObject(), validateFormat(), validateBounds(), enum(), wrongType()

SchemaChoices final readonly class

public/minn/src/Minn/Rest/SchemaChoices.php · 136 lines · Open the class →

anyOf and oneOf the way the REST API's validation judges them (probe rest-schema-choices): a choice's schemas without a type take the choice's own; the refusal for matching none is named from the closest schema, or lists the titles, or is plain; several matches are named by their titles. Schema::validate() asks here first, then applies the schema's own keywords.

new__construct(Minn\Rest\Schema $schema)
methodmatchAny(mixed $value, array $args, string $param): Minn\Runtime\Refusal|array

The first of an anyOf's schemas the value matches, each without a type taking the choice's own, or the refusal for matching none (probe rest-media-actions).

@return array<string, mixed>|Refusal
methodmatchOne(mixed $value, array $args, string $param, string $settle = 'one'): Minn\Runtime\Refusal|array

The one of a oneOf's schemas the value matches, each without a type taking the choice's own; "first" settles for the first match. Matching none is refused as matchAny() refuses it; matching several names them when every one has a title, with their positions.

@return array<string, mixed>|Refusal
methodcombiningError(mixed $value, string $param, array $errors): Minn\Runtime\Refusal

The refusal for a value no schema of a choice matched (probe rest-media-actions). With one schema, its refusal is named. Otherwise the refusals that say only the value is not the schema's type are set aside, and one left is named. Of several left, when the first is an object's, the one whose properties share the most keys with the value is named (the first of a tie, none when none share). Then, when every schema has a title, they are listed; else the plain refusal.

@param list<array{0: Refusal, 1: array<string, mixed>, 2: int|string}> $errors refusal, schema, position
methodcombiningFailure(string $param, ?string $title, string $reason, mixed $position): Minn\Runtime\Refusal

The refusal for a value that matched none of a combinator's schemas, worded from the one that came closest: its title when it has one (an empty title still reads as one), and that schema's own reason.

insideclosestObject(), listed()

SchemaData final class

public/minn/src/Minn/Rest/SchemaData.php · 12 lines · Open the class →

REST schemas captured from the reference for the facade's controller classes (data/rest-schemas.json): an item schema, a collection's parameters or a route argument, by name. Callbacks are not data, so a controller adds its own.

staticof(string $name): array

One captured schema by name; an empty array when there is none. @return array<string, mixed>

@return array<string, mixed>

SchemaValues final class

public/minn/src/Minn/Rest/SchemaValues.php · 198 lines · Open the class →

The value side of JSON Schema, as the reference applies it: what counts as a boolean, integer, array, or object, the coercions to each, the comparisons and date, colour, and uuid parsers the formats use, and the combining walk anyOf and oneOf share. Schema holds the rules; this holds what they are applied to.

staticisBoolean(mixed $value): bool

Whether a value reads as a boolean, the strings included.

statictoBoolean(mixed $value): mixed

The boolean a value reads as.

staticisInteger(mixed $value): bool

Whether a value is a whole number.

staticisArray(mixed $value): bool

Whether a value reads as a list, a comma-separated string included.

statictoArray(mixed $value): array

The list a value reads as.

staticisObject(mixed $value): bool

Whether a value reads as an object.

statictoObject(mixed $value): array

The object a value reads as, as an array.

staticbestType(mixed $value, array|string $types): string

The first of the schema's types, in the order it lists them, that the value reads as (an empty string is a string when that is allowed); '' when none.

staticvaluesEqual(mixed $a, mixed $b): bool

Whether two values are equal by the reference's loose rules.

staticpatternProperty(string $property, array $args): ?array

The schema of the patternProperties entry a property name matches, or null.

staticmatchesPattern(string $pattern, string $value): bool

Whether a value matches a schema pattern, read as the reference reads one: unanchored, # escaped, UTF-8.

statichasUniqueItems(array $items): bool

Whether no two items are equal once their keys are sorted; types stay apart, so 1 and "1" differ.

staticintersectKeys(array $first, array $second): array

The first array's entries whose keys the second has too, at every level where both hold an array.

staticstabilize(mixed $value): mixed

Keys sorted at every level, so two equal structures serialise the same.

staticparseDate(mixed $date, bool $forceUtc = false): int|false

A timestamp for an RFC3339-shaped date, false otherwise; $forceUtc reads the offset as Z.

staticparseHexColor(mixed $color): mixed

A hex colour as given, or false.

staticisUuid(mixed $uuid): bool

Whether a value is a version-4 uuid.

staticlist(mixed $value): array

A comma- or space-separated string as a list.

@return list<string>
insideobjectOf()

SearchController final readonly class

public/minn/src/Minn/Rest/SearchController.php · 122 lines · Open the class →

wp/v2 search over published content: id, title, url, type, and the post type as subtype. Ordered the way the reference orders a search: a title holding every term first, then a title holding any term, then an excerpt match, then a content match, newest first within a rank.

Services final class

public/minn/src/Minn/Rest/Services.php · 417 lines · Open the class →

The objects one REST request shares, each made once, on first use, from the database door and the request. Every getter is typed and names its dependencies in plain constructor calls: there is no autowiring, and get() knows only the names listed here, so a wrong one fails at the first call rather than deep in a handler.

staticforRequest(Minn\Db $db, Minn\Http\Request $request): self

The services for one request, none made yet.

methodget(string $class): object

The service registered under a class name. Only the names in NAMED answer; anything else is a programming error and says so at once.

methodsubjects(): Minn\Rest\Subjects

The record lookups the policy gate asks before it judges a caller.

methoddb(): Minn\Db

The database door.

methodrequest(): Minn\Http\Request

The request being answered.

methodroot(): string

The site root, no trailing slash.

methodcontentDir(): string

wp-content under the site root.

methodusers(): Minn\Content\Users

The users repository.

methodposts(): Minn\Content\Posts

The posts repository.

methodterms(): Minn\Content\Terms

The terms repository.

methodcomments(): Minn\Content\Comments

The comments repository.

methodsite(): Minn\Content\Site

The site's options.

methodwriter(): Minn\Content\PostWriter

The post writer.

methodrevisions(): Minn\Content\Revisions

Revisions and autosaves.

methoduserStyles(): Minn\Theme\UserStyles

The site editor's saved global styles.

methodthemeStyles(): Minn\Theme\ThemeStyles

The active theme's global styles and variations.

methodmenus(): Minn\Content\Menus

Classic menus and their items.

methodinventory(): Minn\Content\Inventory

What wp-content holds: plugins, themes, drop-ins.

methodpermalinks(): Minn\Front\Permalinks

Link building from the permalink structure.

methodurl(): Minn\Rest\RestUrl

REST URL building.

methodcapabilities(): Minn\Auth\Capabilities

The capability engine.

methodcaller(): Minn\Rest\Caller

Who is making this call.

methodsessions(): Minn\Auth\Sessions

The session store.

methodapplicationPasswords(): Minn\Auth\ApplicationPasswords

Application passwords for Basic auth.

methodloader(): Minn\Extension\Loader

The extension loader.

methodtypes(): Minn\Rest\Types

The post types the REST surface knows.

methodtaxonomies(): Minn\Rest\Taxonomies

The taxonomies the REST surface knows.

methoduploads(): Minn\Media\Uploads

The uploads directory.

methodmediaWriter(): Minn\Media\Writer

Stores an upload as an attachment.

methodschema(): Minn\Rest\Schema

The JSON Schema validator, with the reference's filters passed in.

methodapp(): Minn\Admin\App

The Minn Admin bundle on disk.

methodpackages(): Minn\Ops\Packages

Installing and removing themes and extensions.

methodlogs(): Minn\Ops\Logs

The debug log reader.

methodupdates(): Minn\Ops\Updates

Update checks and offers.

methodtranslations(): Minn\Admin\Translations

Locales and the app's catalogs.

methodappearance(): Minn\Admin\Appearance

The app's per-user appearance.

methodhiddenIntegrations(): Minn\Admin\HiddenIntegrations

The app's per-user hidden views.

methodactivityFeed(): Minn\Admin\ActivityFeed

What happened lately.

methoddashboard(): Minn\Admin\Dashboard

The overview payload.

methodreleases(): Minn\Ops\Releases

Minn's own releases, asked of GitHub once a day.

methodengineChangelog(): Minn\Ops\Changelog

Minn's changelog, read from its repository.

methodadminChangelog(): Minn\Ops\Changelog

Minn Admin's changelog, read from its repository.

methodcoreStatus(): Minn\Ops\CoreStatus

Minn's version and the release on offer, behind the app's update banner.

methodengineUpdate(): Minn\Ops\EngineUpdate

The engine replacing itself with a release.

methodnotifications(): Minn\Admin\Notifications

The bell feed.

methoddiagnostics(): Minn\Ops\Diagnostics

The System view's payload.

methodtemplates(): ?Minn\Theme\TemplateIndex

The active block theme's templates; null under a classic theme, where the routes answer as the reference does when it never registered them.

methodtemplateWriter(): ?Minn\Theme\TemplateWriter

Writes block templates; null under a classic theme.

methodpostObject(): Minn\Rest\PostObject

The wp/v2 post shape.

methodtermObject(): Minn\Rest\TermObject

The wp/v2 term shape.

methoduserObject(): Minn\Rest\UserObject

The wp/v2 user shape.

methodmediaObject(): Minn\Rest\MediaObject

The wp/v2 media shape.

methodcommentObject(): Minn\Rest\CommentObject

The wp/v2 comment shape.

insideshare()

Settings final readonly class

public/minn/src/Minn/Rest/Settings.php · 113 lines · Open the class →

The registered settings the Settings views read and write, mapped to the options WordPress stores. Registration order is the payload order.

constSCHEMA = array ( 'blog_public' => array ( 'title' => '', 'description' => '', 'type' => 'integer', 'required' => false, ), 'minn_admin_maintenance' => array ( 'title' => '', 'description' => '', 'type' => 'boolean', 'required' => false, ), 'users_can_register' => array ( 'title' => '', 'description' => '', 'type' => 'integer', 'required' => false, ), 'default_role' => array ( 'title' => '', 'description' => '', 'type' => 'string', 'required' => false, ), 'comment_moderation' => array ( 'title' => '', 'description' => '', 'type' => 'integer', 'required' => false, ), 'comment_registration' => array ( 'title' => '', 'description' => '', 'type' => 'integer', 'required' => false, ), 'show_avatars' => array ( 'title' => '', 'description' => '', 'type' => 'integer', 'required' => false, ), 'title' => array ( 'title' => 'Title', 'description' => 'Site title.', 'type' => 'string', 'required' => false, ), 'description' => array ( 'title' => 'Tagline', 'description' => 'Site tagline.', 'type' => 'string', 'required' => false, ), 'url' => array ( 'title' => '', 'description' => 'Site URL.', 'type' => 'string', 'format' => 'uri', 'required' => false, ), 'email' => array ( 'title' => '', 'description' => 'This address is used for admin purposes, like new user notification.', 'type' => 'string', 'format' => 'email', 'required' => false, ), 'timezone' => array ( 'title' => '', 'description' => 'A city in the same timezone as you.', 'type' => 'string', 'required' => false, ), 'date_format' => array ( 'title' => '', 'description' => 'A date format for all date strings.', 'type' => 'string', 'required' => false, ), 'time_format' => array ( 'title' => '', 'description' => 'A time format for all time strings.', 'type' => 'string', 'required' => false, ), 'start_of_week' => array ( 'title' => '', 'description' => 'A day number of the week that the week should start on.', 'type' => 'integer', 'required' => false, ), 'language' => array ( 'title' => '', 'description' => 'WordPress locale code.', 'type' => 'string', 'required' => false, ), 'use_smilies' => array ( 'title' => '', 'description' => 'Convert emoticons like :-) and :-P to graphics on display.', 'type' => 'boolean', 'required' => false, ), 'default_category' => array ( 'title' => '', 'description' => 'Default post category.', 'type' => 'integer', 'required' => false, ), 'default_post_format' => array ( 'title' => '', 'description' => 'Default post format.', 'type' => 'string', 'required' => false, ), 'posts_per_page' => array ( 'title' => 'Maximum posts per page', 'description' => 'Blog pages show at most.', 'type' => 'integer', 'required' => false, ), 'show_on_front' => array ( 'title' => 'Show on front', 'description' => 'What to show on the front page', 'type' => 'string', 'required' => false, ), 'page_on_front' => array ( 'title' => 'Page on front', 'description' => 'The ID of the page that should be displayed on the front page', 'type' => 'integer', 'required' => false, ), 'page_for_posts' => array ( 'title' => '', 'description' => 'The ID of the page that should display the latest posts', 'type' => 'integer', 'required' => false, ), 'default_ping_status' => array ( 'title' => '', 'description' => 'Allow link notifications from other blogs (pingbacks and trackbacks) on new articles.', 'type' => 'string', 'enum' => array ( 0 => 'open', 1 => 'closed', ), 'required' => false, ), 'default_comment_status' => array ( 'title' => 'Allow comments on new posts', 'description' => 'Allow people to submit comments on new posts.', 'type' => 'string', 'enum' => array ( 0 => 'open', 1 => 'closed', ), 'required' => false, ), 'site_logo' => array ( 'title' => 'Logo', 'description' => 'Site logo.', 'type' => 'integer', 'required' => false, ), 'site_icon' => array ( 'title' => 'Icon', 'description' => 'Site icon.', 'type' => 'integer', 'required' => false, ), )

The body of a write, every registered setting by its key, as the reference describes it.

new__construct(Minn\Content\Site $site)
methodpayload(): array

Every registered setting with its current value.

methodstore(array $body): void

Writes the registered keys in a body, already validated against SCHEMA, in the stored form; unregistered keys are ignored. Without plugins loaded there is nobody to tell (LiveSettings writes through update_option when there is).

SettingsController final readonly class

public/minn/src/Minn/Rest/SettingsController.php · 32 lines · Open the class →

wp/v2/settings: read and write, both behind manage_options; with plugins loaded, every registered setting (LiveSettings).

new__construct(Minn\Rest\Settings $settings, Minn\Rest\Caller $caller, Minn\Rest\LiveSettings $live)
methodsettings(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/settings (signed in)POST /wp/v2/settings (signed in)PUT /wp/v2/settings (signed in)PATCH /wp/v2/settings (signed in)

The site settings: read, or write from the body.

SidebarsController final readonly class

public/minn/src/Minn/Rest/SidebarsController.php · 206 lines · Open the class →

wp/v2/sidebars and wp/v2/widget-types as the reference answers them (probe rest-widgets), for anyone who can edit theme options. A sidebar is a registered one or the inactive widgets, with its wrapping markup, the registered widgets it holds, and its status: active when registered under a classic theme (a block theme renders none). Saving a sidebar's widgets takes them from any other sidebar and sends the ones it drops to the inactive widgets. The widget types are the registered widgets by id base, in id order.

new__construct(Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodsidebars(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/sidebars (public)

Every sidebar: those the sidebars option lists, then any other registered one. Someone who cannot edit theme options sees only the sidebars shown in REST, and is refused when there are none (probe rest-sidebar-classes).

methodsidebar(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/sidebars/{id:[\w-]+} (public)

One sidebar: anyone's to read when it is shown in REST, otherwise for someone who can edit theme options.

methodsave(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/sidebars/{id:[\w-]+} (cap edit_theme_options)PUT /wp/v2/sidebars/{id:[\w-]+} (cap edit_theme_options)PATCH /wp/v2/sidebars/{id:[\w-]+} (cap edit_theme_options)

Saves a sidebar's widgets, in the order given.

methodtypes(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/widget-types (cap edit_theme_options)

The registered widget types, by id base.

methodwidgetType(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/widget-types/{id:[a-zA-Z0-9_-]+} (cap edit_theme_options)

One widget type.

methodencode(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/widget-types/{id:[a-zA-Z0-9_-]+}/encode (cap edit_theme_options)

A widget type's form and preview for settings that are not saved: the settings sent (encoded with their hash) updated from the form's fields, the form for them (number -1 unless one is sent), the widget as the_widget shows it, and the settings encoded again.

staticready(): void

The widgets are registered on init; a runtime that stopped short of it registers them now. Without the runtime there are no widgets to serve.

insideitem(), type(), widgetTypes(), requireSidebar()

StatusesController final readonly class

public/minn/src/Minn/Rest/StatusesController.php · 97 lines · Open the class →

wp/v2/statuses as the reference answers it (probe rest-statuses): every post status that is not internal, a plugin's beside core's, by name, and trash last. A visitor sees the public ones; someone who can edit a type shown in REST sees them all, and only they may ask for the edit context of the list. Each status links to its posts and goes through rest_prepare_status. Like wp/v2/types, the list is keyed by name, so _fields over the whole of it keeps nothing.

Subjects final readonly class

public/minn/src/Minn/Rest/Subjects.php · 53 lines · Open the class →

Whether the record a route capture names exists, for the policy gate to ask before it judges the caller. A post kind reads its type from the {base} capture when the route has one, a term kind its taxonomy; the fixed kinds name their own. Status is not consulted: a trashed post exists, and what the caller may do with it is the policy's question.

new__construct(Minn\Db $db)
methodexists(Minn\Http\Subject $subject, int $id, array $captures): bool

Whether the record exists.

@param array<string, string> $captures the route's captures, for the {base} a kind reads
methodpostOfType(int $id, string $type): bool

Whether a post of the type exists.

methodtermOf(int $id, string $taxonomy): bool

Whether a term of the taxonomy exists.

insidepost(), term()

Taxonomies final class

public/minn/src/Minn/Rest/Taxonomies.php · 48 lines · Open the class →

The taxonomy registry the wp/v2 surface describes: the core set seeded from the reference (data/taxonomies.json) with the reference's own labels, capabilities, and visibility flags.

new__construct(Minn\Rest\RestUrl $url)
methodall(): array

Every taxonomy the surface knows, by slug.

@return array<string, array> keyed by taxonomy slug
methodfind(string $slug): ?array

One taxonomy by slug, or null.

methodforType(string $type): array

The taxonomies attached to a post type, in registry order. @return array<string, array>

@return array<string, array>
staticview(array $taxonomy): array

The reference's view-context projection: the keys an anonymous reader sees.

TemplateObject final readonly class

public/minn/src/Minn/Rest/TemplateObject.php · 101 lines · Open the class →

The wp/v2/templates and wp/v2/template-parts resource.

TemplatesController final readonly class

public/minn/src/Minn/Rest/TemplatesController.php · 324 lines · Open the class →

wp/v2/templates and wp/v2/template-parts: the block theme's templates as one list, whether they come from the theme's files, from a row the site saved over one, or from a plugin. Reading needs only edit_posts (the reference lets an editor or author see the layout); changing anything needs edit_theme_options.

new__construct(Minn\Theme\TemplateIndex $index, Minn\Theme\TemplateWriter $writer, Minn\Rest\TemplateObject $object, Minn\Rest\Caller $caller)
methodtemplates(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/templates (cap edit_posts)

The templates list.

methodparts(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/template-parts (cap edit_posts)

The template parts list.

methodlookupTemplate(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/templates/lookup (cap edit_posts)

The template a slug would use: the first in its hierarchy the theme or the site has.

methodlookupPart(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/template-parts/lookup (cap edit_posts)

The same lookup under the parts route: it searches templates, not parts, as the reference's does.

methodtemplate(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/templates/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts)

One template.

methodpart(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/template-parts/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts)

One template part.

methodcreateTemplate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/templates (cap edit_posts + edit_theme_options)

Creates a template.

methodcreatePart(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/template-parts (cap edit_posts + edit_theme_options)

Creates a template part.

methodsaveTemplate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/templates/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)PUT /wp/v2/templates/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)PATCH /wp/v2/templates/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)

Saves a template.

methodsavePart(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/template-parts/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)PUT /wp/v2/template-parts/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)PATCH /wp/v2/template-parts/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)

Saves a template part.

methoddeleteTemplate(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/templates/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)

Deletes a customised template.

methoddeletePart(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/template-parts/{id:([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)[\/\w%-]+} (cap edit_posts + edit_theme_options)

Deletes a customised template part.

insidelisting(), listed(), lookup(), single(), save(), create(), fields(), delete(), trashed(), record(), readable(), requireWrite(), text()

TermCollectionParams final class

public/minn/src/Minn/Rest/TermCollectionParams.php · 13 lines · implements Minn\Http\RouteParams · Open the class →

A taxonomy's term list parameters as the reference declares them (probe rest-term-lists): the context and Args::TERMS, a hierarchical taxonomy's with a parent and no offset, a flat one's with an offset and no parent. Plugins change them through rest_{taxonomy}_collection_params when the list runs.

staticfor(array $captures): array

The parameters of the list a {base} capture names; none for a base no REST taxonomy has.

TermFilters final class

public/minn/src/Minn/Rest/TermFilters.php · 25 lines · Open the class →

A term a REST read answers with, as plugin code filters it on the reference (probe rest-term-filters): through get_term and get_{taxonomy}. What a filter changes (a count, a name) is what the answer shows. Without the runtime, or with nothing hooked, the engine's own row stands. (Lists run get_terms itself.)

TermListArgs final class

public/minn/src/Minn/Rest/TermListArgs.php · 28 lines · Open the class →

The get_terms arguments a term list request makes, as the reference makes them before rest_{taxonomy}_query (probe rest-term-lists): the taxonomy, each declared parameter the request carries under its query name (include_slugs ordering as slug__in), the offset (the page's unless the list takes one and it is given), the parent a hierarchical list takes, and ids alone for a HEAD request.

staticof(WP_REST_Request $wp, array $registered, string $taxonomy, string $method): array

The arguments before plugins see them.

@param array<string, mixed> $registered the collection, as rest_{taxonomy}_collection_params left it
@return array<string, mixed>

TermObject final readonly class

public/minn/src/Minn/Rest/TermObject.php · 107 lines · Open the class →

The wp/v2 category and tag objects.

new__construct(Minn\Db $db, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodview(Minn\Content\TermRecord $term, string $restBase): array

A term as its REST base shows it, through rest_prepare_{taxonomy} when a plugin hooks it.

methodurl(): Minn\Rest\RestUrl

The REST URL builder.

staticconfig(string $restBase): array

The taxonomy behind a rest_base.

@return array{taxonomy: string, has_parent: bool, post_arg: string, post_base: string}
staticregistered(string $restBase): ?array

A taxonomy plugin code registered to show in REST under wp/v2, by its REST base (probe rest-plugin-types), as a term route's config: its terms nest when it is hierarchical, and its posts are those of the first type it belongs to.

@return array{taxonomy: string, has_parent: bool, post_arg: string, post_base: string}|null
insideviewFields(), allowedVerbs()

TermsController final readonly class

public/minn/src/Minn/Rest/TermsController.php · 193 lines · Open the class →

wp/v2 categories, tags, and pattern categories: list, single, and the create/update/delete the taxonomy admin drives.

new__construct(Minn\Db $db, Minn\Content\Terms $terms, Minn\Content\Site $site, Minn\Rest\TermObject $object, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request, string $base): Minn\Http\ResponseGET /wp/v2/{base:categories|tags|wp_pattern_category} (public)

A taxonomy's term list as the reference serves it: the request's get_terms (wp_get_object_terms for a post's) through rest_{taxonomy}_collection_params and rest_{taxonomy}_query, counted by wp_count_terms without the page.

methodsingle(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseGET /wp/v2/{base:categories|tags|wp_pattern_category}/{id:[\d]+} (public; term {id} must exist; edit context: cap manage_categories)

One category or tag.

methodcreate(Minn\Http\Request $request, string $base): Minn\Http\ResponsePOST /wp/v2/{base:categories} (cap manage_categories)POST /wp/v2/{base:tags|wp_pattern_category} (cap edit_posts)

Tags and pattern categories are open to edit_posts holders; categories need manage_categories.

methodupdate(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponsePOST /wp/v2/{base:categories|tags|wp_pattern_category}/{id:[\d]+} (cap manage_categories; term {id} must exist)PUT /wp/v2/{base:categories|tags|wp_pattern_category}/{id:[\d]+} (cap manage_categories; term {id} must exist)PATCH /wp/v2/{base:categories|tags|wp_pattern_category}/{id:[\d]+} (cap manage_categories; term {id} must exist)

Updates a category or tag.

methoddelete(Minn\Http\Request $request, string $base, string $id): Minn\Http\ResponseDELETE /wp/v2/{base:categories|tags|wp_pattern_category}/{id:[\d]+} (cap manage_categories; term {id} must exist)

The default category is capability-denied before the force check.

staticcreateCapability(string $taxonomy): string

What creating a term needs (probe rest-plugin-caps): a plugin's taxonomy, its edit_terms when hierarchical and its assign_terms when flat; categories, manage_categories; the others, edit_posts.

insiderequireParent(), termsReadable()

Types final class

public/minn/src/Minn/Rest/Types.php · 190 lines · Open the class →

The engine's registry of built-in post types, seeded from the observed contract (src/data/types.json) with _links attached at runtime.

new__construct(Minn\Rest\RestUrl $url, array $declared = array ( ))
@param array<string, array<string, mixed>> $declared extra types from active extensions
methodall(): array

Every post type the surface knows, by slug.

@return array<string, array> keyed by type slug
methodfind(string $slug): ?array

One post type by slug, or null.

methodslugForRestBase(string $base): ?string

The type behind a rest_base, or null.

methoddeclaredBases(): array

The rest_base of every type an extension declared or plugin code registered under wp/v2. @return list<string>

@return list<string>
methodrouteBases(): array

Every base the {base} routes serve: the declared post types' and the taxonomies plugin code registered to show in REST under wp/v2.

@return list<string>
methodisDeclared(string $slug): bool

Whether the engine serves this type through its {base} routes: an extension declared it, or plugin code registered it to show in REST under wp/v2 (probe rest-plugin-types).

methodsearchable(): array

The types a post search covers: the built-in post and page, the ones extensions declare, and the public types plugin code shows in REST, as the reference's post search handler takes them.

@return list<string>
methodrestBase(string $slug): string

The rest_base of a type slug.

insidecore(), registered(), servedRegistered()

UrlDetails final class

public/minn/src/Minn/Rest/UrlDetails.php · 64 lines · Open the class →

A page's details as the editor's link previews read them (probe rest-embed-details), from the document's head: everything up to its closer, the whole page when it has none. The first title, as text with its tags gone; the first link whose rel names "icon" (not an apple-touch-icon); the first meta named description; the first meta whose property is og:image or og:image:url. Twitter tags, og:title and og:description count for nothing. Each is trimmed. An address with no host hangs off the page's root whatever its own path, and one with no scheme takes the page's.

staticof(string $html, string $url): array

The page's four details, read from its markup; addresses made whole against the page's own. @return array{title: string, icon: string, description: string, image: string}

@return array{title: string, icon: string, description: string, image: string}
insideread(), text(), absolute()

UserCollectionParams final class

public/minn/src/Minn/Rest/UserCollectionParams.php · 11 lines · implements Minn\Http\RouteParams · Open the class →

The users list's parameters as the reference declares them (probe rest-user-lists): the context, Args::USERS, and has_published_posts taking the REST post types by name. Plugins change them through rest_user_collection_params when the list runs.

staticfor(array $captures): array

The list's parameters (the route has no captures).

UserListArgs final class

public/minn/src/Minn/Rest/UserListArgs.php · 47 lines · Open the class →

The WP_User_Query arguments a user list request makes, as the reference makes them before rest_user_query (probe rest-user-lists): each declared parameter the request carries under its query name (a search wrapped in wildcards), the offset (the page's when none is given), the orderby name mapped to the query's, the published authors (every REST post type, for a reader who may not list users), the authors shorthand, the search columns (a reader who may not list users searches names and logins only), and ids alone for a HEAD request.

staticof(WP_REST_Request $wp, array $registered, string $method, array $types, string $reader): array

The arguments before plugins see them, for a reader who is a 'lister' (may list users) or 'public'.

@param array<string, mixed> $registered the collection, as rest_user_collection_params left it
@param array<string, string> $types the REST post types, by name
@return array<string, mixed>

UserObject final readonly class

public/minn/src/Minn/Rest/UserObject.php · 103 lines · Open the class →

The wp/v2 user objects: the public view shape and the edit-context shape.

new__construct(Minn\Db $db, Minn\Content\Users $users, Minn\Front\Permalinks $permalinks, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller)
methodview(Minn\Content\UserRecord $u): array

A user as the view context shows one, through rest_prepare_user when a plugin hooks it.

methodedit(Minn\Content\UserRecord $u): array

A user as the edit context shows one, through rest_prepare_user when a plugin hooks it.

staticavatarUrls(string $email): array

Gravatar URLs in the sizes the reference emits (sha256 of the email).

insideviewFields(), editFields()

UsersController final readonly class

public/minn/src/Minn/Rest/UsersController.php · 379 lines · Open the class →

wp/v2 users: me, list, single, and the create/update/delete-with-reassign the Users view drives.

new__construct(Minn\Db $db, Minn\Content\Users $users, Minn\Content\Site $site, Minn\Rest\UserObject $object, Minn\Rest\RestUrl $url, Minn\Rest\Caller $caller, Minn\Auth\Roles $roles, Minn\Rest\Schema $schema)
methodme(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/users/me (public)

The signed-in user.

methodupdateMe(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/users/me (signed in)PUT /wp/v2/users/me (signed in)PATCH /wp/v2/users/me (signed in)

Updates the signed-in user; signed out there is no such user (404), and the Allow header leaves the writes out.

methoddeleteMe(Minn\Http\Request $request): Minn\Http\ResponseDELETE /wp/v2/users/me (cap delete_users)

Deletes the signed-in user as users/{id} deletes any; signed out there is no such user (404).

methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/users (public)

The users list as the reference serves it: the request's WP_User_Query through rest_user_collection_params and rest_user_query (a reader who may not list users sees published authors only), its totals (counted again without the page when it found none), and the users it found.

methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/users/{id:[\d]+} (public; user {id} must exist)

One user.

methodcreate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/users (cap create_users)

Engine-created users carry real scheme hashes and the full default meta set.

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/users/{id:[\d]+} (cap edit_user on {id}; user {id} must exist)PUT /wp/v2/users/{id:[\d]+} (cap edit_user on {id}; user {id} must exist)PATCH /wp/v2/users/{id:[\d]+} (cap edit_user on {id}; user {id} must exist)

Updates a user.

methoddelete(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/users/{id:[\d]+} (cap delete_users; user {id} must exist)

reassign is REQUIRED (checked before the user lookup), and so is force.

insidelistAllowed(), totals(), hasPublishedContent(), validRole(), validLocale(), assignableRole(), validEmail(), loginRefusal()

ViewConfigPatch final class

public/minn/src/Minn/Rest/ViewConfigPatch.php · 151 lines · Open the class →

A patch to a view configuration (WP_View_Config_Data; probe rest-view-config), applied as the reference applies it. Only schema version 1 is taken, and only the documented keys: each is set (its value replaced), merged (maps merged key by key, lists joined with their members matched by identity), replaced (merged, but lists replaced whole) or removed (keys from a map, members from a list by identity). A null in a patch removes what it names. What a caller got wrong comes back as problems: the function to name and the words.

constKEYS = array ( 0 => 'default_view', 1 => 'default_layouts', 2 => 'view_list', 3 => 'form', )
staticapply(array $config, array $patch, mixed $version, string $mode): array

A configuration with a patch applied in a mode (set, merge, replace, remove), and the problems met: [function or null for the mode's own method, words].

@param array<string, mixed> $config
@param array<mixed> $patch
@return array{0: array<string, mixed>, 1: list<array{0: ?string, 1: string}>}
staticstripNulls(mixed $value): mixed

A value with every null in it removed, lists closed up.

staticidentity(mixed $item): ?string

The identity a list member is matched by: a word itself, a map's id or slug; null for none.

insidemerge(), mergeList(), remove()

WidgetObject final readonly class

public/minn/src/Minn/Rest/WidgetObject.php · 55 lines · Open the class →

A widget as wp/v2/widgets shows it (probe rest-widgets): its id and base, its sidebar, its markup as that sidebar wraps it (nothing among the inactive widgets) and, in the edit context, its settings form and its settings: serialized and base64 encoded, signed with wp_hash, and raw when the widget shows its instance in REST. Links to itself, its type and its sidebar; through rest_prepare_widget.

new__construct(Minn\Rest\RestUrl $url)
methodview(string $widgetId, string $sidebarId, Minn\Rest\Context $context): array

A widget in a sidebar, its form and settings in the edit context. @return array<string, mixed>

@return array<string, mixed>
staticinstance(WP_Widget $object, int $number): array

A widget's settings, encoded and signed, and raw when it shows them. @return array<string, mixed>

@return array<string, mixed>
insideform()

WidgetsController final readonly class

public/minn/src/Minn/Rest/WidgetsController.php · 169 lines · Open the class →

wp/v2/widgets as the reference answers it (probe rest-widgets), for anyone who can edit theme options: the registered widgets of every sidebar (or one), a widget created under the next free number for its type, its settings saved through the widget's own update (raw when the widget shows its instance in REST, encoded with a matching hash, or from its form's fields) and registered at once, a widget moved between sidebars, and a widget deleted, or without force sent to the inactive widgets.

new__construct(Minn\Rest\WidgetObject $object, Minn\Rest\Caller $caller)
methodlist(Minn\Http\Request $request): Minn\Http\ResponseGET /wp/v2/widgets (cap edit_theme_options)

The registered widgets, sidebar by sidebar.

methodsingle(Minn\Http\Request $request, string $id): Minn\Http\ResponseGET /wp/v2/widgets/{id:[\w\-]+} (cap edit_theme_options)

One widget.

methodcreate(Minn\Http\Request $request): Minn\Http\ResponsePOST /wp/v2/widgets (cap edit_theme_options)

Creates a widget in a sidebar (the inactive widgets unless one is named).

methodupdate(Minn\Http\Request $request, string $id): Minn\Http\ResponsePOST /wp/v2/widgets/{id:[\w\-]+} (cap edit_theme_options)PUT /wp/v2/widgets/{id:[\w\-]+} (cap edit_theme_options)PATCH /wp/v2/widgets/{id:[\w\-]+} (cap edit_theme_options)

Saves a widget's settings and moves it, as the body asks.

methoddelete(Minn\Http\Request $request, string $id): Minn\Http\ResponseDELETE /wp/v2/widgets/{id:[\w\-]+} (cap edit_theme_options)

Deletes a widget with force; without, sends it to the inactive widgets.

insidesaveInstance(), newInstance(), sidebarOf()