Divi renders any referenced et_pb_layout, whatever its scope term. A normal entry with usages now gets the dry run, confirm, P8a, backup and the site-wide GlobalItem scope; lit-divi-delete's notice and the library-list detail name its pages too. The usage scan is the same single query. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
143 lines
5.8 KiB
PHP
143 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Support;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
|
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
|
use LIT\MCP\Integrations\Divi\Library\GlobalUsages;
|
|
use LIT\MCP\Integrations\Divi\Library\LibraryAccess;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
|
use Mockery;
|
|
use WP_Post;
|
|
|
|
/**
|
|
* The Divi Library of the tests: the real builder entries of tests/Fixtures/divi/stage3 by their
|
|
* IDs, a page (post 7), WordPress stubs by post ID, a store answering from ITEMS, Divi roles and
|
|
* the library access built from them.
|
|
*/
|
|
final class LibraryFixtures
|
|
{
|
|
public const PAGE = 7;
|
|
|
|
/** @var array<int, array{string, string, bool}> ID => [fixture file, layout type, global]. */
|
|
public const ITEMS = [
|
|
1054 => ['library--layout-ganze-seite.html', 'layout', false],
|
|
1055 => ['library--library-abschnitt.html', 'section', false],
|
|
1056 => ['library--library-zeile.html', 'row', false],
|
|
1057 => ['library--library-modul.html', 'module', false],
|
|
1058 => ['library-global--globaler-abschnitt.html', 'section', true],
|
|
1059 => ['library-global--globales-modul.html', 'module', true],
|
|
];
|
|
|
|
/** @param array<string, mixed> $fields */
|
|
public static function item(int $id, array $fields = []): WP_Post
|
|
{
|
|
return new WP_Post(array_merge([
|
|
'ID' => $id,
|
|
'post_type' => DiviLibraryStore::POST_TYPE,
|
|
'post_status' => 'publish',
|
|
'post_title' => '[Fixture] ' . $id,
|
|
'post_content' => DiviFixtures::markup('stage3/' . self::ITEMS[$id][0]),
|
|
], $fields));
|
|
}
|
|
|
|
/** @param array<string, mixed> $fields */
|
|
public static function page(string $content, array $fields = []): WP_Post
|
|
{
|
|
return new WP_Post(array_merge([
|
|
'ID' => self::PAGE,
|
|
'post_type' => 'page',
|
|
'post_status' => 'draft',
|
|
'post_title' => 'Start',
|
|
'post_content' => $content,
|
|
], $fields));
|
|
}
|
|
|
|
/**
|
|
* WordPress stubs for $posts (get_post by ID) and a user with every capability but $denied.
|
|
*
|
|
* @param list<WP_Post> $posts
|
|
* @param list<string> $denied
|
|
*/
|
|
public static function given(array $posts, array $denied = []): void
|
|
{
|
|
$byId = [];
|
|
foreach ($posts as $post) {
|
|
$byId[(int) $post->ID] = $post;
|
|
}
|
|
Functions\when('get_post')->alias(
|
|
static fn (mixed $id): ?WP_Post => $byId[(int) ($id instanceof WP_Post ? $id->ID : $id)] ?? null
|
|
);
|
|
Functions\when('current_user_can')->alias(
|
|
static fn (string $cap, mixed ...$args): bool => ! in_array($cap, $denied, true)
|
|
);
|
|
Functions\when('get_post_type_object')->justReturn(null);
|
|
Functions\when('get_permalink')->justReturn('https://kunde.test/?page_id=7');
|
|
Functions\when('get_edit_post_link')->alias(static fn (mixed $post): string => sprintf(
|
|
'https://kunde.test/wp-admin/post.php?post=%d&action=edit',
|
|
$post instanceof WP_Post ? $post->ID : (int) $post
|
|
));
|
|
Functions\when('add_query_arg')->alias(
|
|
static fn (string $key, string $value, string $url): string => $url . '&' . $key . '=' . $value
|
|
);
|
|
Functions\when('get_preview_post_link')->justReturn('https://kunde.test/?page_id=7&preview=true');
|
|
}
|
|
|
|
/** A store that answers isGlobal()/layoutType() from ITEMS (no database). */
|
|
public static function store(): DiviLibraryStore
|
|
{
|
|
$store = Mockery::mock(DiviLibraryStore::class)->makePartial();
|
|
$store->allows('isGlobal')->andReturnUsing(static fn (int $id): bool => self::ITEMS[$id][2] ?? false);
|
|
$store->allows('layoutType')->andReturnUsing(static fn (int $id): ?string => self::ITEMS[$id][1] ?? null);
|
|
|
|
return $store;
|
|
}
|
|
|
|
/** @param list<string> $deniedRoles Divi role options the user lacks. */
|
|
public static function roles(array $deniedRoles = []): DiviRoles
|
|
{
|
|
return new DiviRoles(static fn (string $cap): bool => ! in_array($cap, $deniedRoles, true));
|
|
}
|
|
|
|
/** @param list<string> $deniedRoles */
|
|
public static function access(
|
|
array $deniedRoles = [],
|
|
bool $writable = true,
|
|
?DiviLibraryStore $store = null
|
|
): LibraryAccess {
|
|
return new LibraryAccess($store ?? self::store(), self::roles($deniedRoles), static fn (): bool => $writable);
|
|
}
|
|
|
|
/**
|
|
* GlobalUsages whose query answers $rows (a closure gets the entry ID on every call); the rights checks
|
|
* are the real ones, with the post-cache functions stubbed.
|
|
*
|
|
* @param list<array{id: int, status: string, type: string}>|\Closure(int): list<array{id: int, status: string, type: string}> $rows
|
|
*/
|
|
public static function usages(array|\Closure $rows, ?DiviRoles $roles = null): GlobalUsages
|
|
{
|
|
Functions\when('_get_non_cached_ids')->returnArg(1);
|
|
Functions\when('_prime_post_caches')->justReturn(null);
|
|
Functions\when('wp_cache_delete')->justReturn(true);
|
|
$usages = Mockery::mock(GlobalUsages::class, [null, $roles ?? self::roles()])->makePartial();
|
|
$usages->allows('rows')->andReturnUsing(static fn (int $id): array => is_array($rows) ? $rows : $rows($id));
|
|
|
|
return $usages;
|
|
}
|
|
|
|
/** @return list<array{id: int, status: string, type: string}> One row per ID. */
|
|
public static function rows(array $ids, string $status = 'publish', string $type = 'page'): array
|
|
{
|
|
return array_map(static fn (int $id): array => ['id' => $id, 'status' => $status, 'type' => $type], $ids);
|
|
}
|
|
|
|
public static function loader(?LibraryAccess $access = null): DiviPageLoader
|
|
{
|
|
return new DiviPageLoader(DiviFixtures::schema(), new Parser(), $access ?? self::access());
|
|
}
|
|
}
|