'object', 'properties' => array_merge(DiviSchema::write(), [ 'id' => ['type' => 'string', 'description' => 'Pfad-ID des zu duplizierenden Knotens.'], ]), 'required' => ['post_id', 'tree_rev', 'id'], 'additionalProperties' => false, ]; } public function annotations(): array { return Annotations::write(false); } public function isDirect(): bool { return false; } /** Like lit/divi-move: TreeEditor::duplicate() copies stored bytes, no composition of client values. */ protected function baseFeatures(): array { return [DiviFeature::PageRead, DiviFeature::PageWrite, DiviFeature::Catalog, DiviFeature::DesignRead]; } protected function change(DiviPage $page, array $input, DiviComposition $composition): DiviChange|WP_Error { $id = is_string($input['id'] ?? null) ? $input['id'] : ''; $node = $page->index->find($id); if ($node === null) { return DiviErrors::unknownNode($id, $page->post); } $copy = $composition->editor()->duplicate($page->index, $node); if ($copy->isDivi() && ! $copy->isOpaque()) { // The raw stored desktop value (never NodeSummary::label()'s display-shortened // text): a long or multi-line label must be copied verbatim, only " (Kopie)" added. // Setting only the ".desktop.value" leaf keeps every other breakpoint untouched. // No client input: the stored label plus a constant suffix, so no choke point (S39). $raw = AttrMap::desktop($node->attrs(), 'module.meta.adminLabel'); $label = is_string($raw) ? $raw : ''; $copy->setAttrs(AttrMap::set( $copy->attrs(), 'module.meta.adminLabel.desktop.value', ($label === '' ? $node->shortName() : $label) . ' (Kopie)' )); } return (new DiviChange(sprintf('Knoten %s dupliziert', $id)))->record($copy, 'duplicated'); } }