fix: lit/divi-node-copy from a library entry needs the insert rights (I-1, L5)

A library entry as source now requires LibraryAccess::canInsert() (add_library,
writable library group) and follows lit-library-insert's Tools-tab state
(disabled -> unsupported). DiviPageLoader::library() gets its caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 04:06:56 +02:00
co-authored by Claude Opus 5.5
parent 186f7af7ca
commit 1b42f9b640
5 changed files with 97 additions and 12 deletions
+5 -2
View File
@@ -58,13 +58,16 @@ final class DiviIntegration implements Integration
* "Divi-Bibliothek" not disabled, ruling P9); null = always.
* @param (Closure(): bool)|null $diviWritable Whether lit/divi-insert is enabled – lit/divi-node-copy
* follows it (ruling P9, E9); null = always.
* @param (Closure(): bool)|null $libraryInsertable Whether lit/library-insert is enabled –
* lit/divi-node-copy follows it for library entries as source (audit L5); null = always.
*/
public function __construct(
private ContentSchema $schema,
private ChangeLog $log,
?Closure $libraryWritable = null,
?Closure $libraryVisible = null,
private ?Closure $diviWritable = null
private ?Closure $diviWritable = null,
private ?Closure $libraryInsertable = null
) {
$this->modules = new DiviModules();
$this->design = new DesignSystemReader();
@@ -119,7 +122,7 @@ final class DiviIntegration implements Integration
new DiviDeleteAbility($writer),
new DiviDuplicateNodeAbility($writer),
new DiviReplaceTextAbility($this->schema, $loader, $writer),
new DiviNodeCopyAbility($writer, $loader, $this->diviWritable),
new DiviNodeCopyAbility($writer, $loader, $this->diviWritable, $this->libraryInsertable),
new DiviDynamicSourcesAbility($this->schema, new DynamicContentMap(), $this->access->roles()),
];
}
@@ -19,15 +19,22 @@ defined('ABSPATH') || exit;
* hiding) and its own source_tree_rev, because path IDs are only valid for the state they were read
* in; the source is only read, so it needs no lock. Target: like lit-divi-insert (tree_rev lock,
* StructureRules, all or nothing, DiviWriter::commit with GlobalEdits – a global target entry never
* gets a nested instance). Follows lit/divi-insert's Tools-tab state (ruling P9/E9).
* gets a nested instance). Follows lit/divi-insert's Tools-tab state (ruling P9/E9). A library entry
* as source is an insert from the library (E3, audit L5): it needs LibraryAccess::canInsert()
* (add_library, writable group "Divi-Bibliothek") and follows lit-library-insert's Tools-tab state.
*/
final class DiviNodeCopyAbility extends DiviWriteAbility
{
/** @param (Closure(): bool)|null $diviWritable Whether lit/divi-insert is enabled; null = always. */
/**
* @param (Closure(): bool)|null $diviWritable Whether lit/divi-insert is enabled; null = always.
* @param (Closure(): bool)|null $libraryInsertable Whether lit/library-insert is enabled (gates
* library entries as source); null = always.
*/
public function __construct(
DiviWriter $writer,
private DiviPageLoader $loader,
private ?Closure $diviWritable = null
private ?Closure $diviWritable = null,
private ?Closure $libraryInsertable = null
) {
parent::__construct($writer);
}
@@ -74,7 +81,10 @@ final class DiviNodeCopyAbility extends DiviWriteAbility
return false;
}
/** lit/divi-insert's Tools-tab state, then the source's own rights – in permission() and execute(). */
/**
* lit/divi-insert's Tools-tab state, then the source's own rights and, for a library entry as
* source, the rights of an insert from the library – in permission() and execute().
*/
protected function precheck(array $input): ?WP_Error
{
if ($this->diviWritable !== null && ! ($this->diviWritable)()) {
@@ -86,8 +96,25 @@ final class DiviNodeCopyAbility extends DiviWriteAbility
}
$source = $this->loader->load($input['source_post_id'] ?? null, false);
if ($source instanceof WP_Error) {
return $source;
}
return $source instanceof WP_Error ? $source : null;
return $source->isLibraryItem() ? $this->libraryInsertRefusal() : null;
}
/** E3/audit L5: copying out of a library entry is inserting from the library (lit-library-insert). */
private function libraryInsertRefusal(): ?WP_Error
{
if ($this->libraryInsertable !== null && ! ($this->libraryInsertable)()) {
return Errors::unsupported(
'Einfügen aus der Divi-Bibliothek ist für diese Verbindung abgeschaltet (lit-library-insert '
. 'deaktiviert oder Gruppe „Divi-Bibliothek“ auf „nur lesen“).',
'Einen Administrator fragen: Einstellungen → LIT MCP → Tools.'
);
}
return $this->loader->library()?->canInsert();
}
protected function change(DiviPage $page, array $input, DiviComposition $composition): DiviChange|WP_Error
+8 -1
View File
@@ -89,12 +89,19 @@ final class Plugin
$libraryWritable = static function () use (&$policy): bool {
return $policy instanceof ExposurePolicy && $policy->groupWritable(LibraryAccess::GROUP);
};
// Audit L5: lit/divi-node-copy from a library entry follows lit/library-insert's Tools-tab state.
$libraryInsertable = static function () use (&$registry, &$policy): bool {
return $registry instanceof IntegrationRegistry
&& $policy instanceof ExposurePolicy
&& $policy->allows('lit/library-insert', $registry->abilities());
};
$divi = new DiviIntegration(
$schema,
$changeLog,
$libraryWritable,
libraryVisible: $libraryVisible,
diviWritable: $diviWritable
diviWritable: $diviWritable,
libraryInsertable: $libraryInsertable
);
$registry = new IntegrationRegistry(
new CoreIntegration(self::pluginDir() . '/docs/guides'),