'Abschnitt', 'row' => 'Zeile', 'module' => 'Modul']; private const SCOPES = ['normal', 'global']; private NewLibraryEntry $entries; public function __construct( private LibraryAccess $access, private DiviPageLoader $loader, private DiviWriter $writer, DiviSaver $saver, private ChangeLog $log, DiviModules $modules ) { $this->entries = new NewLibraryEntry($access, $saver, $modules); } public function name(): string { return 'lit/library-save'; } public function label(): string { return 'In der Divi-Bibliothek speichern'; } public function description(): string { return 'Speichert einen Divi-Knoten (Abschnitt, Zeile oder Modul) als neuen Eintrag der Divi-Bibliothek – ' . 'normal oder global (scope). Der Knoten auf der Seite bleibt unverändert. post_id, tree_rev und die ' . 'Pfad-ID aus lit-divi-outline; Kategorien und Schlagwörter nur vorhandene. Globale Einträge mit ' . 'lit-library-insert (mode "global") auf Seiten einfügen.'; } public function inputSchema(): array { return [ 'type' => 'object', 'properties' => [ 'post_id' => [ 'type' => 'integer', 'minimum' => 1, 'description' => 'Seite (oder Eintrag) mit dem Knoten.', ], 'tree_rev' => ['type' => 'string', 'description' => 'tree_rev aus lit-divi-outline dieser Seite.'], 'id' => ['type' => 'string', 'description' => 'Pfad-ID des Knotens, z. B. "s1".'], 'title' => ['type' => 'string', 'minLength' => 1, 'maxLength' => 200], 'scope' => ['type' => 'string', 'enum' => self::SCOPES, 'description' => 'Standard: normal.'], 'categories' => ['type' => 'array', 'maxItems' => 10, 'items' => ['type' => 'string']], 'tags' => ['type' => 'array', 'maxItems' => 10, 'items' => ['type' => 'string']], ], 'required' => ['post_id', 'tree_rev', 'id', 'title'], 'additionalProperties' => false, ]; } public function outputSchema(): ?array { return ToolResult::schema( ['library_id' => ['type' => 'integer'], 'tree_rev' => ['type' => 'string']], ['library_id', 'tree_rev'] ); } public function annotations(): array { return Annotations::write(false); } public function isDirect(): bool { return false; } public function permission(array $input): bool|WP_Error { $refusal = $this->access->canSave(self::isGlobal($input)); if ($refusal !== null) { return $refusal; } $page = $this->loader->load($input['post_id'] ?? null, false); return $page instanceof WP_Error ? $page : true; } /** @throws WriteFailed After the insert (the new entry is rolled back first, ruling P6). */ public function execute(array $input): mixed { if (! in_array($input['scope'] ?? 'normal', self::SCOPES, true)) { return Errors::invalidInput('scope ist „normal“ oder „global“; nichts wurde angelegt.', [ 'scope' => 'normal oder global', ]); } $refusal = $this->access->canSave(self::isGlobal($input)); if ($refusal !== null) { return $refusal; } $page = $this->loader->load($input['post_id'] ?? null, false); if ($page instanceof WP_Error) { return $page; } if (! is_string($input['tree_rev'] ?? null) || $input['tree_rev'] !== $page->treeRev()) { return Errors::revMismatch( 'Die Seite wurde seit dem letzten Lesen geändert (tree_rev passt nicht); nichts wurde angelegt.', DiviPageLoader::OUTLINE_HINT ); } $id = is_string($input['id'] ?? null) ? $input['id'] : ''; $node = $page->index->find($id); if ($node === null) { return DiviErrors::unknownNode($id, $page->post); } $spec = $this->entries->spec( $page, $node, $input, self::isGlobal($input), $this->writer->composition($page)->storedGuard() ); if ($spec instanceof WP_Error) { return $spec; } return $this->create($page, $spec, $node, $input); } /** * @param array $input * @return array * @throws WriteFailed */ private function create(DiviPage $page, LibraryItemSpec $spec, Block $node, array $input): array { $draft = new NewDraft(null, $this->log, $this->name(), DiviLibraryStore::POST_TYPE, 'Bibliothekseintrag'); $new = $this->entries->create($spec, $node, $draft); $libraryId = $new['id']; $followUp = (new PostSaveFollowUp( $this->name(), $this->log, $libraryId, 'der Bibliothekseintrag', $new['warnings'] ))->afterSave($input); $created = $followUp->reread(); $kind = (self::TYPE_LABELS[$spec->layoutType] ?? $spec->layoutType) . ($spec->global ? ', global' : ''); $followUp->log( DiviLibraryStore::POST_TYPE, sprintf('Bibliothek: „%s“ angelegt (%s) aus Eintrag %d', $spec->title, $kind, (int) $page->post->ID) ); $content = $created !== null ? (string) $created->post_content : NewLibraryEntry::content($node); $result = $followUp->result(sprintf('Bibliothekseintrag „%s“ angelegt (%s).', $spec->title, $kind), $created) ->with('library_id', $libraryId) ->with('tree_rev', Rev::of($content)) ->urls(null, (string) get_edit_post_link($libraryId, 'raw')); if ($spec->global) { $result->warning( 'Der Knoten auf der Seite bleibt eine eigenständige Kopie; verknüpfte Instanzen mit lit-library-insert ' . '(mode "global") einfügen.' ); } return $result->toArray(); } /** @param array $input */ private static function isGlobal(array $input): bool { return ($input['scope'] ?? 'normal') === 'global'; } }