WpLinkTargets answers the library, internal "_" types and every type without a Divi post_link_url option like a missing ID; replace-text strips library IDs only from global subtrees. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
122 lines
6.2 KiB
PHP
122 lines
6.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||
|
||
use Brain\Monkey\Functions;
|
||
use InvalidArgumentException;
|
||
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
|
||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||
use LIT\MCP\Integrations\Divi\Pages\WpLinkTargets;
|
||
use LIT\MCP\Integrations\Divi\Pages\WpValueSanitizer;
|
||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||
use LIT\MCP\Tests\Unit\TestCase;
|
||
use WP_Post;
|
||
|
||
final class WpLinkTargetsTest extends TestCase
|
||
{
|
||
public function testOnlyReadableLivingPostsCanBeLinked(): void
|
||
{
|
||
$posts = [
|
||
839 => new WP_Post(['ID' => 839, 'post_type' => 'page', 'post_status' => 'draft']),
|
||
841 => new WP_Post(['ID' => 841, 'post_type' => 'page', 'post_status' => 'trash']),
|
||
842 => new WP_Post(['ID' => 842, 'post_type' => 'page', 'post_status' => 'private']),
|
||
843 => new WP_Post(['ID' => 843, 'post_type' => 'revision', 'post_status' => 'inherit']),
|
||
844 => new WP_Post(['ID' => 844, 'post_type' => 'page', 'post_status' => 'auto-draft']),
|
||
845 => new WP_Post(['ID' => 845, 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => 0]),
|
||
846 => new WP_Post(['ID' => 846, 'post_type' => 'attachment', 'post_status' => 'trash']),
|
||
847 => new WP_Post(['ID' => 847, 'post_type' => 'attachment', 'post_status' => 'private']),
|
||
];
|
||
Functions\when('get_post')->alias(static fn (int $id): ?WP_Post => $posts[$id] ?? null);
|
||
Functions\when('current_user_can')->alias(
|
||
static fn (string $cap, int $id): bool => $cap === 'read_post' && $id !== 842
|
||
);
|
||
|
||
$links = new WpLinkTargets(self::linkable());
|
||
|
||
self::assertSame('page', $links->postTypeOf(839));
|
||
self::assertNull($links->postTypeOf(841), 'trashed');
|
||
self::assertNull($links->postTypeOf(842), 'not readable');
|
||
self::assertNull($links->postTypeOf(843), 'revision');
|
||
self::assertNull($links->postTypeOf(844), 'auto-draft');
|
||
self::assertNull($links->postTypeOf(900), 'missing');
|
||
self::assertNull($links->postTypeOf(845), 'unattached attachment');
|
||
self::assertNull($links->postTypeOf(846), 'trashed attachment');
|
||
self::assertNull($links->postTypeOf(847), 'attachment with an own status (ruling P10: no attachment)');
|
||
}
|
||
|
||
/** Post types Divi offers post_link_url_<type> for in these tests (et_builder_get_public_post_types). */
|
||
private static function linkable(): \Closure
|
||
{
|
||
return static fn (string $type): bool => in_array($type, ['page', 'post', 'attachment', 'lit_person'], true);
|
||
}
|
||
|
||
/**
|
||
* Ruling Q14: a type without a Divi link option (the library, internal "_" types, any other) is no
|
||
* link target – answered like a missing ID, so {{link:}} tells nobody which IDs are library entries.
|
||
*/
|
||
public function testTypesWithoutADiviLinkOptionAreNotFound(): void
|
||
{
|
||
$posts = [
|
||
1058 => new WP_Post(['ID' => 1058, 'post_type' => 'et_pb_layout', 'post_status' => 'publish']),
|
||
1070 => new WP_Post(['ID' => 1070, 'post_type' => '_internal', 'post_status' => 'publish']),
|
||
1071 => new WP_Post(['ID' => 1071, 'post_type' => 'wp_template', 'post_status' => 'publish']),
|
||
1072 => new WP_Post(['ID' => 1072, 'post_type' => 'lit_person', 'post_status' => 'publish']),
|
||
];
|
||
Functions\when('get_post')->alias(static fn (int $id): ?WP_Post => $posts[$id] ?? null);
|
||
Functions\when('current_user_can')->justReturn(true);
|
||
$everything = new WpLinkTargets(static fn (string $type): bool => true);
|
||
|
||
$links = new WpLinkTargets(self::linkable());
|
||
self::assertNull($links->postTypeOf(1058), 'library entry');
|
||
self::assertNull($links->postTypeOf(1070), 'internal type');
|
||
self::assertNull($links->postTypeOf(1071), 'type without link option');
|
||
self::assertSame('lit_person', $links->postTypeOf(1072));
|
||
self::assertNull($everything->postTypeOf(1058), 'the library never, whatever Divi lists');
|
||
self::assertNull($everything->postTypeOf(1070), 'internal types never, whatever Divi lists');
|
||
self::assertNull((new WpLinkTargets())->postTypeOf(1072), 'without Divi no type has a link option');
|
||
}
|
||
|
||
/**
|
||
* Ruling Q14: {{link:}} and the full form answer a library ID exactly like a missing ID – with and
|
||
* without the library right (read_post on the entry), so no guessed ID reveals a library entry.
|
||
*/
|
||
public function testALibraryIdIsAnsweredLikeAMissingId(): void
|
||
{
|
||
Functions\when('get_post')->alias(static fn (int $id): ?WP_Post => $id === 1058
|
||
? new WP_Post(['ID' => 1058, 'post_type' => 'et_pb_layout', 'post_status' => 'publish'])
|
||
: null);
|
||
$links = new WpLinkTargets(self::linkable());
|
||
$placeholders = new Placeholders(DiviFixtures::designSystem(), $links);
|
||
$ref = new VariableRef(DiviFixtures::designSystem(), ['post_link_url_page' => ['post_id' => 'select_post']]);
|
||
$sanitizer = new WpValueSanitizer($links);
|
||
$answer = static function (callable $call, string $id): string {
|
||
try {
|
||
$call($id);
|
||
|
||
return 'accepted';
|
||
} catch (InvalidArgumentException $e) {
|
||
return str_replace($id, '<ID>', $e->getMessage());
|
||
}
|
||
};
|
||
$short = static fn (string $id): string => $placeholders->resolve('{{link:' . $id . '}}');
|
||
$full = static fn (string $id): string => $ref->canonical(
|
||
'$variable({"type":"content","value":{"name":"post_link_url_page","settings":{"post_id":"' . $id . '"}}})$',
|
||
$sanitizer,
|
||
true
|
||
);
|
||
|
||
foreach (['with the library right' => true, 'without it' => false] as $case => $right) {
|
||
Functions\when('current_user_can')->justReturn($right);
|
||
foreach (['{{link:}}' => $short, 'post_link_url_page' => $full] as $form => $call) {
|
||
$library = $answer($call, '1058');
|
||
self::assertNotSame('accepted', $library, $case . ' ' . $form);
|
||
self::assertSame($answer($call, '1066'), $library, $case . ' ' . $form);
|
||
self::assertStringNotContainsString('et_pb_layout', $library, $case . ' ' . $form);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|