Outline, node-get, write outlines, the render summary and replace-text's opaque-node match name no library ID unless the library group is visible and the user has divi_library; read-only still names them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
174 lines
6.4 KiB
PHP
174 lines
6.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Integrations\Divi\Pages;
|
||
|
||
use LIT\MCP\Abilities\Ability;
|
||
use LIT\MCP\Abilities\Annotations;
|
||
use LIT\MCP\Abilities\Errors;
|
||
use LIT\MCP\Abilities\JsonMap;
|
||
use LIT\MCP\Abilities\Rev;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\NodeSummary;
|
||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||
use LIT\MCP\Integrations\Divi\Schema\ShortformMap;
|
||
use WP_Error;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* lit/divi-node-get: all attributes of one node in the full form (spec §5.4, §5.7) –
|
||
* attribute path => breakpoint map – plus the current short-form values.
|
||
*/
|
||
final class DiviNodeGetAbility implements Ability
|
||
{
|
||
public function __construct(private DiviPageLoader $loader, private DesignSystemReader $design)
|
||
{
|
||
}
|
||
|
||
public function name(): string
|
||
{
|
||
return 'lit/divi-node-get';
|
||
}
|
||
|
||
public function label(): string
|
||
{
|
||
return 'Divi-Knoten lesen';
|
||
}
|
||
|
||
public function description(): string
|
||
{
|
||
return 'Liest einen Knoten der Divi-Seite vollständig: attrs als Attributpfad → Werte je Breakpoint '
|
||
. '(Vollform, z. B. "title.decoration.font.font": {"desktop": {"value": {…}}}), die Kurzform-Werte, '
|
||
. 'Presets und die IDs der Kinder. Pfad-IDs aus lit-divi-outline. Werte wie $variable(…)$ (globale '
|
||
. 'Farben, Variablen, dynamische Inhalte) werden unverändert angezeigt, beim Schreiben aber erneut '
|
||
. 'geprüft, nie ungeprüft übernommen.';
|
||
}
|
||
|
||
public function inputSchema(): array
|
||
{
|
||
return [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'post_id' => ['type' => 'integer', 'minimum' => 1],
|
||
'id' => ['type' => 'string', 'description' => 'Pfad-ID aus lit-divi-outline, z. B. "s0.r0.c1.m2".'],
|
||
],
|
||
'required' => ['post_id', 'id'],
|
||
'additionalProperties' => false,
|
||
];
|
||
}
|
||
|
||
public function outputSchema(): ?array
|
||
{
|
||
return [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'post_id' => ['type' => 'integer'],
|
||
'tree_rev' => ['type' => 'string'],
|
||
'rev' => ['type' => 'string'],
|
||
'id' => ['type' => 'string'],
|
||
'parent' => ['type' => ['string', 'null']],
|
||
'type' => ['type' => 'string'],
|
||
'label' => ['type' => 'string'],
|
||
'editable' => ['type' => 'boolean'],
|
||
'attrs' => ['type' => 'object'],
|
||
'shortform' => ['type' => 'object'],
|
||
'presets' => [
|
||
'type' => 'array',
|
||
'items' => [
|
||
'type' => 'object',
|
||
'properties' => ['id' => ['type' => 'string'], 'name' => ['type' => 'string']],
|
||
'required' => ['id', 'name'],
|
||
],
|
||
],
|
||
'builder_version' => ['type' => ['string', 'null']],
|
||
'children' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||
'global' => ['type' => ['object', 'null']],
|
||
],
|
||
'required' => ['post_id', 'tree_rev', 'rev', 'id', 'type', 'editable', 'attrs', 'shortform', 'children'],
|
||
];
|
||
}
|
||
|
||
public function annotations(): array
|
||
{
|
||
return Annotations::read();
|
||
}
|
||
|
||
public function isDirect(): bool
|
||
{
|
||
return true;
|
||
}
|
||
|
||
public function permission(array $input): bool|WP_Error
|
||
{
|
||
$page = $this->loader->load($input['post_id'] ?? null, false);
|
||
|
||
return $page instanceof WP_Error ? $page : true;
|
||
}
|
||
|
||
public function execute(array $input): mixed
|
||
{
|
||
$page = $this->loader->load($input['post_id'] ?? null, false);
|
||
if ($page instanceof WP_Error) {
|
||
return $page;
|
||
}
|
||
|
||
$id = is_string($input['id'] ?? null) ? $input['id'] : '';
|
||
if (preg_match(NodeIndex::ID_PATTERN, $id) !== 1) {
|
||
return Errors::invalidInput('id ist keine Pfad-ID.', ['id' => 'z. B. "s0.r1.c0.m2"']);
|
||
}
|
||
|
||
$block = $page->index->find($id);
|
||
if ($block === null) {
|
||
return DiviErrors::unknownNode($id, $page->post);
|
||
}
|
||
|
||
$names = $this->design->read()->presetNames();
|
||
$editable = $block->isDivi() && ! $block->isOpaque();
|
||
$version = $block->attrs()['builderVersion'] ?? null;
|
||
|
||
$global = NodeSummary::globalRef($block);
|
||
if ($global !== null && $this->loader->libraryIdsVisible()) {
|
||
$global['hint'] = sprintf(
|
||
'Globales Modul: Inhalt im Bibliothekseintrag %s bearbeiten (lit-divi-outline mit post_id %s); '
|
||
. 'jede Änderung dort wirkt auf alle Seiten mit diesem Modul.',
|
||
$global['id'],
|
||
$global['id']
|
||
);
|
||
} elseif ($global !== null) {
|
||
// Ruling P13: which entry is shown stays unknown without library access (AttrMap::flatten
|
||
// already leaves globalModule/globalParent out of attrs).
|
||
$global = [
|
||
'id' => null,
|
||
'block_name' => $global['block_name'],
|
||
'hint' => 'Globales Modul aus der Divi-Bibliothek – nur lesbar; dieses Konto hat keinen Zugang zur '
|
||
. 'Divi-Bibliothek.',
|
||
];
|
||
}
|
||
|
||
return [
|
||
'post_id' => (int) $page->post->ID,
|
||
'tree_rev' => $page->treeRev(),
|
||
'rev' => Rev::ofPost($page->post),
|
||
'id' => $id,
|
||
'parent' => $page->index->parentId($block),
|
||
'type' => $block->isDivi() ? $block->shortName() : $block->name(),
|
||
'label' => NodeSummary::label($block),
|
||
'editable' => $editable,
|
||
'attrs' => JsonMap::of(AttrMap::flatten($block->attrs())),
|
||
'shortform' => JsonMap::of(ShortformMap::values($block->shortName(), $block->attrs())),
|
||
'presets' => array_map(
|
||
static fn (string $presetId): array => ['id' => $presetId, 'name' => $names[$presetId] ?? ''],
|
||
NodeSummary::presetIds($block)
|
||
),
|
||
'builder_version' => is_string($version) ? $version : null,
|
||
'children' => $editable
|
||
? array_values(array_filter(array_map([$page->index, 'idOf'], $block->children())))
|
||
: [],
|
||
'global' => $global,
|
||
];
|
||
}
|
||
}
|