Files
WP-LIT-MCP/src/Integrations/Divi/Pages/DiviMoveAbility.php
T

150 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Pages;
use LIT\MCP\Abilities\Annotations;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\Compose\LoopContext;
use LIT\MCP\Integrations\Divi\Compose\LoopScope;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use WP_Error;
defined('ABSPATH') || exit;
/**
* lit/divi-move: moves a node (with everything inside) to parent / before / after – also into
* another row or section, as long as the structure rules allow it (spec §5.7). Read-only
* nodes (global modules, foreign blocks) move as a whole, unchanged. Loop content that leaves its loop scope (out
* of a loop, into one of another kind) is moved with a warning – Divi allows it (rulings X3, A11).
*/
final class DiviMoveAbility extends DiviWriteAbility
{
public function name(): string
{
return 'lit/divi-move';
}
public function label(): string
{
return 'Divi-Knoten verschieben';
}
public function description(): string
{
return 'Verschiebt einen Divi-Knoten samt Inhalt an eine neue Stelle (parent, before oder after; auch in '
. 'andere Zeilen oder Abschnitte). 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 verschiebenden Knotens.']],
DiviSchema::position()
),
'required' => ['post_id', 'tree_rev', 'id'],
'additionalProperties' => false,
];
}
public function annotations(): array
{
return Annotations::write(false);
}
/**
* No client value enters the tree (no DynamicSources): load and save (PageRead, PageWrite), StructureRules
* need the catalog, the quality warnings of the result read the design system (DesignRead).
*/
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);
}
$editor = $composition->editor();
$position = $editor->position($page->index, $input);
if (is_string($position)) {
return DiviErrors::position($position);
}
// Both scopes from the tree before the move: the new parent never lies inside $node (TreeEditor::move()).
$from = self::loopAt($page->index, $page->index->parentBlock($node));
$to = self::loopAt($page->index, $position['parent']);
$problem = $editor->move($page->index, $node, $position);
if ($problem !== null) {
return Errors::invalidInput(
'Verschieben nicht möglich; nichts wurde gespeichert.',
['position' => $problem]
);
}
$change = (new DiviChange(sprintf('Knoten %s verschoben', $id)))->record($node, 'moved');
$names = self::loopContent($node);
if ($names !== [] && $from->kind() !== $to->kind()) {
$change->warning(self::loopWarning($names, $from, $to));
}
return $change;
}
/**
* The loop_* options $block and its descendants show in the scope around $block; a block that runs its own
* loop keeps its content's scope, read-only blocks are not entered.
*
* @return list<string>
*/
private static function loopContent(Block $block): array
{
if ($block->isOpaque() || LoopContext::enabled($block->attrs())) {
return [];
}
$names = [];
$attrs = $block->attrs();
array_walk_recursive($attrs, static function (mixed $value) use (&$names): void {
$name = is_string($value) ? VariableRef::nameOf($value) : null;
if ($name !== null && str_starts_with($name, 'loop_')) {
$names[] = $name;
}
});
foreach ($block->children() as $child) {
array_push($names, ...self::loopContent($child));
}
return array_values(array_unique($names));
}
/** @param list<string> $names */
private 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
);
}
}