$input */ abstract protected function change(DiviPage $page, array $input, DiviComposition $composition): DiviChange|WP_Error; /** * What the tool needs whatever entry it changes; diviFeatures() adds what the entry post_id names (a library * entry or Theme Builder layout is saved with its own capability, D5). * * @return list */ abstract protected function baseFeatures(): array; public function diviFeatures(array $input): array { return FeatureSets::merge($this->baseFeatures(), FeatureSets::ofEntry($input['post_id'] ?? null, true)); } public function outputSchema(): ?array { return ToolResult::schema([ 'tree_rev' => ['type' => 'string'], // Global library entries (answer F2): the pages that show the entry, and the backup // made when revisions are off (also for live Theme Builder layouts, ruling Q2). 'affected' => ['type' => 'array', 'items' => ['type' => 'object']], 'affected_count' => ['type' => 'integer'], 'hidden_count' => ['type' => 'integer'], 'backup_id' => ['type' => 'integer'], // Library entries and Theme Builder layouts: their revision in WordPress' revision screen (audit L3 – // lit/revision-restore takes only content types). 'restore_url' => ['type' => 'string'], // Theme Builder layouts: the enabled templates that show the layout – those the user may edit; the // others only as a count (ruling Q22). 'templates' => ['type' => 'array', 'items' => ['type' => 'object']], 'hidden_templates' => ['type' => 'integer'], ], ['tree_rev']); } public function isDirect(): bool { return true; } final public function permission(array $input): bool|WP_Error { $refusal = $this->precheck($input); if ($refusal !== null) { return $refusal; } $page = $this->writer->page($input); return $page instanceof WP_Error ? $page : true; } final public function execute(array $input): mixed { $refusal = $this->precheck($input); if ($refusal !== null) { return $refusal; } $page = $this->writer->page($input); if ($page instanceof WP_Error) { return $page; } $change = $this->change($page, $input, $this->writer->composition($page)); if ($change instanceof WP_Error) { return $this->writer->notice($page, $change); } return $this->writer->commit($this->name(), $page, $change, $input); } /** * Checks of a tool beyond the page (rights and Tools-tab gates of another group) – run in * permission() and again in execute(). Null = none. * * @param array $input */ protected function precheck(array $input): ?WP_Error { return null; } /** * Ruling X3: the loop scope at $block – its nearest loop-enabled ancestor-or-self (LoopContext, X1); none at * the top level. $newAttrs (node ID => attrs) replaces the stored attrs of nodes this call changes. The index * is the tree loaded for this call, never a client value. Opaque blocks are never entered by NodeIndex, so one * can only be $block itself – and every tool refuses to write into or onto it. * * @param array> $newAttrs */ protected static function loopAt(NodeIndex $index, ?Block $block, array $newAttrs = []): LoopScope { $chain = []; for ($node = $block; $node !== null; $node = $index->parentBlock($node)) { array_unshift($chain, $newAttrs[(string) $index->idOf($node)] ?? $node->attrs()); } return LoopContext::fromChain($chain); } /** @param list $names */ protected static function loopWarning(array $names, LoopScope $from, LoopScope $to): string { $where = $to->active() ? sprintf( 'jetzt in einer Divi-Schleife vom Typ: %s (vorher: %s); passen sie nicht zu dieser Art, zeigt Divi ' . 'sie leer oder mit Daten eines anderen Objekts mit derselben ID', $to->label(), $from->active() ? $from->label() : 'keine Schleife' ) : 'jetzt außerhalb jeder Divi-Schleife; Divi zeigt sie dort leer'; return sprintf( 'Schleifen-Inhalte (%s) liegen %s. Zurückverschieben oder die Inhalte anpassen.', implode(', ', $names), $where ); } }