Files
WP-LIT-MCP/src/Integrations/Divi/Library/LibraryAccess.php
T

180 lines
6.6 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\Library;
use Closure;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Integrations\WordPress\PostGuard;
use WP_Error;
use WP_Post;
defined('ABSPATH') || exit;
/**
* Rights on Divi Library entries (spec §4.5, E3 – never laxer than Divi): the library at all needs
* edit_posts and the Divi role option divi_library; one entry additionally edit_post on it (Divi's
* builder routes do not check that, F9). The Divi tools reach library entries only through
* item(); every other ID goes on to PostGuard unchanged.
* Not final so abilities can be tested with a mock.
*/
class LibraryAccess
{
/** Tool group "Divi-Bibliothek" (Tools tab); its switches gate library entries (E9, ruling P9). */
public const GROUP = 'divi-library';
/**
* @param (Closure(): bool)|null $writable Whether the Tools tab lets this connection write to the
* library (group "Divi-Bibliothek" not read-only or disabled); null = always.
* @param (Closure(): bool)|null $visible Whether library entries exist for this connection at all
* (group "Divi-Bibliothek" not disabled, ruling P9); null = always.
*/
public function __construct(
private DiviLibraryStore $store,
private DiviRoles $roles,
private ?Closure $writable = null,
private ?Closure $visible = null
) {
}
public function store(): DiviLibraryStore
{
return $this->store;
}
public function roles(): DiviRoles
{
return $this->roles;
}
/**
* The library entry $postId after the checks for reading, or their refusal; null when $postId
* names no library entry (the caller goes on with PostGuard). With the library hidden (ruling
* P9) or without the library right at all (ruling P12), an entry is refused exactly like an ID
* that does not exist, so its type and existence stay unknown; only a user with the library
* right who lacks edit_post on the entry gets forbidden (like pages). $forWrite adds canEdit().
*/
public function item(mixed $postId, bool $forWrite): WP_Post|WP_Error|null
{
$id = PostGuard::id($postId);
$post = $id === null ? null : get_post($id);
if (! $post instanceof WP_Post || $post->post_type !== DiviLibraryStore::POST_TYPE) {
return null;
}
if (($this->visible !== null && ! ($this->visible)()) || $this->canList() !== null) {
return PostGuard::notFound((int) $post->ID);
}
return ($forWrite ? $this->canEdit($post) : $this->canRead($post)) ?? $post;
}
/** Listing the library: edit_posts and the Divi role option divi_library. */
public function canList(): ?WP_Error
{
if (! current_user_can('edit_posts')) {
return Errors::forbidden('Keine Berechtigung, die Divi-Bibliothek zu nutzen.');
}
return $this->roles->refusal('divi_library');
}
/** Reading one entry: canList() and edit_post on the entry. */
public function canRead(WP_Post $item): ?WP_Error
{
$refusal = $this->canList();
if ($refusal !== null) {
return $refusal;
}
return current_user_can('edit_post', (int) $item->ID)
? null
: Errors::forbidden(sprintf('Keine Berechtigung, Bibliothekseintrag %d zu bearbeiten.', (int) $item->ID));
}
/**
* Changing one entry: canRead(), the role option save_library, the Tools-tab gate and a live
* entry; a global entry additionally needs edit_global_library (E3 – Divi's builder route
* does not check edit_post on the entry, F9; LIT MCP does).
*/
public function canEdit(WP_Post $item): ?WP_Error
{
$refusal = $this->canRead($item) ?? $this->roles->refusal('save_library') ?? $this->writableGate();
if ($refusal !== null) {
return $refusal;
}
if ((string) $item->post_status !== 'publish') {
return Errors::invalidInput(
sprintf(
'Bibliothekseintrag %d ist nicht aktiv (Status „%s“); nichts wurde gespeichert.',
(int) $item->ID,
(string) $item->post_status
),
['post_id' => 'nur aktive Bibliothekseinträge'],
'Den Eintrag zuerst in der Divi-Bibliothek wiederherstellen.'
);
}
return $this->isGlobal($item) ? $this->roles->refusal('edit_global_library') : null;
}
/**
* Creating an entry: canList(), publish_posts of et_pb_layout (library entries are always
* published, F2), the role option save_library and the Tools-tab gate; a global entry
* additionally edit_global_library (ruling P7 – the same option as for editing one, F12).
*/
public function canSave(bool $global = false): ?WP_Error
{
$refusal = $this->canList();
if ($refusal !== null) {
return $refusal;
}
$type = get_post_type_object(DiviLibraryStore::POST_TYPE);
$publish = is_object($type) && isset($type->cap->publish_posts)
? (string) $type->cap->publish_posts
: 'publish_posts';
if (! current_user_can($publish)) {
return Errors::forbidden('Keine Berechtigung, Einträge der Divi-Bibliothek anzulegen (publish_posts).');
}
$refusal = $this->roles->refusal('save_library') ?? $this->writableGate();
if ($refusal !== null || ! $global) {
return $refusal;
}
return $this->roles->refusal('edit_global_library');
}
/**
* Inserting from the library into a page (E3): canList(), the role option add_library (Divi's
* "Aus der Bibliothek hinzufügen") and a group "Divi-Bibliothek" that is not read-only or
* disabled (E9). Reading the entry itself stays item() (ruling P5).
*/
public function canInsert(): ?WP_Error
{
return $this->canList() ?? $this->roles->refusal('add_library') ?? $this->writableGate();
}
/** Refusal while the Tools tab keeps this connection from writing to the library. */
public function writableGate(): ?WP_Error
{
if ($this->writable === null || ($this->writable)()) {
return null;
}
return Errors::forbidden(
'Schreiben in die Divi-Bibliothek ist für diese Verbindung abgeschaltet (Gruppe „Divi-Bibliothek“ auf '
. '„nur lesen“ oder deaktiviert).',
'Einen Administrator fragen: Einstellungen → LIT MCP → Tools.'
);
}
public function isGlobal(WP_Post $item): bool
{
return $this->store->isGlobal((int) $item->ID);
}
}