fix: bounded usage scan and count-only unreadable posts in global edits (L1, L4)

GlobalUsages runs one query per request (ID, status, type – no content),
checks read_post/edit_post in chunks of 200 with a primed and released post
cache, lists at most 50 readable posts and refuses a confirmed global edit
above 2000 live references (unsupported, edit in the Divi builder). Posts the
caller may not read appear only as hidden_count / blocked_hidden_count;
blocked_ids names readable posts only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 04:12:38 +02:00
co-authored by Claude Opus 5.5
parent bd28c32513
commit e8f69f67ea
12 changed files with 445 additions and 167 deletions
+24
View File
@@ -8,6 +8,7 @@ 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;
@@ -111,6 +112,29 @@ final class LibraryFixtures
return new LibraryAccess($store ?? self::store(), self::roles($deniedRoles), static fn (): bool => $writable);
}
/**
* GlobalUsages whose query answers $rows (a closure is asked 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(): 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])->makePartial();
$usages->allows('rows')->andReturnUsing(static fn (): array => is_array($rows) ? $rows : $rows());
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());