Every file under src/ exits outside WordPress (defined('ABSPATH') ||
exit; after the use block, so a direct request no longer prints a fatal
error with the server path); divi-convert-fixture.php gets the WP_CLI
guard the other integration scripts have. A unit test keeps both true;
PSR1 side-effects warning excluded for the guard.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
84 lines
2.8 KiB
PHP
84 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Integrations\Divi\Pages;
|
|
|
|
use LIT\MCP\Abilities\Annotations;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
|
use WP_Error;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
/**
|
|
* lit/divi-duplicate-node: copies a node with everything inside right behind the original
|
|
* (spec §5.7). The copy's admin label gets " (Kopie)"; read-only nodes (global modules,
|
|
* foreign blocks) are copied unchanged, as a whole, byte-identical reference.
|
|
*/
|
|
final class DiviDuplicateNodeAbility extends DiviWriteAbility
|
|
{
|
|
public function name(): string
|
|
{
|
|
return 'lit/divi-duplicate-node';
|
|
}
|
|
|
|
public function label(): string
|
|
{
|
|
return 'Divi-Knoten duplizieren';
|
|
}
|
|
|
|
public function description(): string
|
|
{
|
|
return 'Dupliziert einen Divi-Knoten samt Inhalt direkt hinter das Original; das Admin-Label der Kopie endet '
|
|
. 'auf „(Kopie)“. tree_rev aus lit-divi-outline ist Pflicht.';
|
|
}
|
|
|
|
public function inputSchema(): array
|
|
{
|
|
return [
|
|
'type' => '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;
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|