* `[summary, target, before, after]` to be undoable) * - preview: `fn(array $args): array{summary,before,after}` * - undo: `fn(array $before, array $after): array` * - stage: `fn(array $args, Preview $preview): array{summary,before,after}` * - render: `fn(mixed $staged): void`, called while the chat's preview * is shown, for the one browser that holds it, with what * `stage` kept; it lays that over the page with WordPress's * own filters and writes nothing * * Any callback may answer `['error' => 'plain words']` instead. * * @api */ final class AbilitySpec { public const READ = 'read'; public const WRITE = 'write'; /** @var array */ private array $input = ['properties' => [], 'required' => [], 'type' => 'object']; private ?Closure $permission = null; private ?Closure $run = null; private ?Closure $preview = null; private ?Closure $undo = null; private ?Closure $stage = null; private ?Closure $render = null; private bool $consent = false; /** @var array */ private array $aliases = []; private function __construct( private readonly string $name, private readonly string $kind, private readonly string $label, private readonly string $description, ) { } public static function read(string $name, string $label, string $description): self { return new self($name, self::READ, $label, $description); } public static function write(string $name, string $label, string $description): self { return new self($name, self::WRITE, $label, $description); } /** @param array $schema the arguments' JSON schema (an object) */ public function withInput(array $schema): self { $copy = clone $this; $copy->input = $schema; return $copy; } public function withPermission(Closure $permission): self { $copy = clone $this; $copy->permission = $permission; return $copy; } public function withRun(Closure $run): self { $copy = clone $this; $copy->run = $run; return $copy; } public function withPreview(Closure $preview): self { $copy = clone $this; $copy->preview = $preview; return $copy; } public function withUndo(Closure $undo): self { $copy = clone $this; $copy->undo = $undo; return $copy; } public function withStage(Closure $stage): self { $copy = clone $this; $copy->stage = $stage; return $copy; } public function withRender(Closure $render): self { $copy = clone $this; $copy->render = $render; return $copy; } /** Runs only after the person's explicit go-ahead. */ public function withConsent(): self { $copy = clone $this; $copy->consent = true; return $copy; } /** Earlier names of this ability, so changes journaled under them can still be undone. */ public function withAliases(string ...$names): self { $copy = clone $this; $copy->aliases = array_values(array_unique(array_merge($copy->aliases, $names))); return $copy; } public function name(): string { return $this->name; } public function kind(): string { return $this->kind; } public function label(): string { return $this->label; } public function description(): string { return $this->description; } /** @return array */ public function input(): array { return $this->input; } public function permission(): ?Closure { return $this->permission; } public function run(): ?Closure { return $this->run; } public function preview(): ?Closure { return $this->preview; } public function undo(): ?Closure { return $this->undo; } public function stage(): ?Closure { return $this->stage; } public function render(): ?Closure { return $this->render; } public function consent(): bool { return $this->consent; } /** @return array */ public function aliases(): array { return $this->aliases; } }