Releases: AdminBolt/plugin-sdk
Releases · AdminBolt/plugin-sdk
Release list
1.0.7: Give a plugin the transport it talks to everything else with
An integration plugin has two halves. admin() and client() cover the one that faces the panel, signing and retrying every call. The other half is the service the plugin exists to connect the panel to, and the SDK had nothing to say about it: a plugin built its own curl handle, which meant the half most worth testing could not be reached from a test. Plugin::http() hands back the injected client, or a default curl one built from the configured timeout. A test that passes Plugin::create() a fake now sees both halves. Its two arguments are for the case that differs from the default, a service behind a certificate nothing in the trust store signed, and are ignored when a client was injected so that asking for them in production code cannot defeat a fake in a test. FakeHttpClient moves out of the SDK's own tests and ships. Every plugin needs exactly it, and writing it again in each one is how plugins end up with slightly different ideas of what an HTTP failure looks like. Manifest::directory() is the plugin's own root, for reading a file it ships rather than one the panel wrote. Resolved through realpath, because it ends up in front of an operator as a path to type: a manifest discovered from public/index.php would otherwise hand back a path with "/public/.." in the middle of it. Also corrects what UiResponse::redirect() says about itself. It claimed an external URL opens in a new tab. The panel follows only its own paths and logs that it refused anything else, so a link out belongs in a Text::markdown() component, where the viewer can see where it goes before they take it.
1.0.6: Let a manifest say where a plugin belongs and what it looks like
Two fields the panel's plugin list now reads. "category" is one word from a fixed vocabulary and becomes the tab a plugin is found under; a word the panel does not know is filed under other rather than refused, which is the bargain the rest of the manifest already strikes. "screenshots" is up to six pictures shipped inside the plugin, optionally captioned. A screenshot is a file in the plugin: png, jpg, webp, avif or gif, on a relative path that stays inside it. Not SVG, which can carry script, and never a remote URL - the panel serves these itself rather than sending every administrator's browser to fetch an address a manifest chose. Say nothing and the panel still looks in screenshots/ and then at a screenshot.png in the root, so a repository that already has one needs no change. Themes get the paragraph they need: the list draws a theme as its picture, because what a theme is cannot be written in a sentence, and a theme that names no category is filed under appearance. The docs also now say what the panel will not read from a manifest: who wrote the plugin. That badge comes from a list the panel ships.
1.0.5: Return the panel's word for which account a call is about
A plugin's API key is an admin key, because a client-panel page is drawn for whichever account is looking. Left at that, a plugin could name any account on the server in X-Hosting-Account, which is not what the approval screen offered when it said "for the account viewing the plugin". So the panel no longer lets a plugin choose. It puts a signed grant naming the account in the envelope it sends, and the client API acts only on what that grant says. This returns it. clientFor() is the only thing a plugin needs to know about: it already scoped a call to the account a request concerns, and it now carries the grant along with the username, because the two are the same fact and a plugin should not be able to send one without the other. forAccount() takes the grant as a second argument and sends it when there is one. Nothing is invented when there is not: a plugin naming an account by hand is the case the panel refuses, and making that call look authorised here would only move the refusal further from the mistake. Hook deliveries read the grant from the same place in their context, ready for the panel to start sending it. A plugin acting on a hook that carries none is refused, exactly as a page would be. Needs the matching panel release: a panel without it ignores the header, and a panel with it refuses a plugin that does not send one.
1.0.4: Let a plugin ship a stylesheet the panel can wear
A theme is a manifest block: a name, a stylesheet inside the plugin, and the panels it applies to. The panel loads its own stylesheet first and this one after it, so a theme is a file of token overrides rather than a rebuild of everything, and docs/themes.md says which tokens are worth starting from. The path is checked like a path, because this is the one thing a plugin ships that reaches a browser as the plugin wrote it: relative, inside the plugin, and a .css file. The panel serves it as text/css and will serve nothing else. A plugin that ships only a theme now needs no runtime block and no entrypoint. It is not a program: there is no listener for the panel to call, no secret to hold, and an entrypoint would be a PHP file that never runs. Anything the panel does have to call still has to declare one, which is what the rest of that rule says.
1.0.3: Give plugins a place to draw and a vocabulary for where
A plugin's pages waited for somebody to go and look at them. Slots are the other half: a line in the footer, a note under the navigation, a warning above every admin page. $plugin->slot() answers one, and what it returns is a page description, so there is nothing new to learn about what a slot may contain. SlotPosition is the catalogue of where. It is written out here, all sixty-two of them, rather than being whatever the panel happens to render with: a plugin that declares "footer" should keep drawing in the footer for as long as the panel has one, whoever built the layer underneath by then. The panel keeps the map from these names to its own view layer, and a change of view layer moves that map rather than every plugin in the wild. Names are added and never repurposed, exactly as hook names are. Three kinds of position are deliberately missing: the document head, the script block and the stylesheet block take markup and nothing else, and a plugin sends a description rather than markup. The manifest validates a slot the way it validates a page, and a position with a typo in it is told what was probably meant, because "sidebar.nav.ends" is a mistake somebody will make more than once.
1.0.2: Let a page say how big its frame should be
A frame the panel sizes for the plugin is a frame that is wrong for half of them. A console wants everything the window has; a settings page wants to be as tall as its content and no taller. Three fields, and all three only mean something for a page the plugin draws itself, so declaring one on a declarative page is refused rather than accepted and ignored: the panel draws those with its own components and decides how tall they are. height: "auto" is the one worth explaining. The frame is sandboxed out of the panel's origin, which is what keeps a plugin's scripts away from the panel's DOM and cookies, and it means the panel cannot measure the frame's document either. So the front end reports its own height, and the panel clamps what it is told and keeps a floor under it, because a plugin that never reports one must be a page rather than an empty strip.
1.0.1: Merge pull request #1 from AdminBolt/plugin-commands
Let a plugin run the commands it declared
1.0.0: Let plugins put their own pages in the panel
A plugin describes a page and the panel draws it with its own components. It never emits HTML. That is the whole reason to prefer this over an iframe. The page matches the rest of the panel, follows the operator's theme, works at phone width, and is escaped by the panel, so a plugin cannot inject markup into the panel even by accident: there is no markup to inject. The panel already iframes CloudLinux's UI, and the coupling that took (knowing its bundles, its config file, its feature gates) is the thing this design exists to avoid. Page, Section, Stat, Table, Form, Alert and Text cover what a plugin page actually needs. Colours are the panel's semantic names rather than hex values, so a plugin cannot fight the theme or produce something unreadable in dark mode, and column types tell the panel how to format a value rather than the plugin formatting it in the wrong timezone. Iframes stay available for a terminal or an existing single-page app, declared as render: iframe and proxied by the panel so the plugin still needs no public port. Security-relevant decisions: The viewer identity travels in the same signed envelope as a hook delivery, because it is what a plugin scopes its data by. An unsigned one would be a way to read another account's data through a plugin. clientFor() now takes a page request as well as a hook, so scoping to the account being viewed stays the path of least resistance. Only registered pages and actions are reachable. A request naming anything else is refused before plugin code runs, so an action is not callable because someone guessed it. A secret field drops its value on serialisation whatever the plugin set, so a stored credential is never served back to a browser. Show that one is set with the placeholder instead. A handler that throws returns a flat message; the detail goes to the plugin log rather than to the person looking at the page. Poll intervals floor at five seconds, since every poll is a request to the plugin.