"Detached" now means for lit/tb-assign what lit/tb-list shows as detached: DiviThemeBuilder::isListedAnywhere() looks for any _et_template row (live, draft or Theme Builder library post). A template another Theme Builder post lists is refused with or without reattach; nothing is written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
189 lines
8.0 KiB
PHP
189 lines
8.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Support;
|
||
|
||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||
use LIT\MCP\Integrations\Divi\ThemeBuilder\Template;
|
||
use LIT\MCP\Integrations\Divi\ThemeBuilder\ThemeBuilderAccess;
|
||
use Mockery;
|
||
use WP_Post;
|
||
|
||
/**
|
||
* The Theme Builder of the tests: the real builder posts of tests/Fixtures/divi/stage3 (live TB post 1064,
|
||
* default template 1065, template 1066 with header 1061, body 1062, footer 1063 – both disabled), a store
|
||
* answering from Template values and the access built from them. Write methods of the store are real
|
||
* unless a test mocks them.
|
||
*/
|
||
final class ThemeBuilderFixtures
|
||
{
|
||
public const THEME_BUILDER = 1064;
|
||
public const DEFAULT_TEMPLATE = 1065;
|
||
public const TEMPLATE = 1066;
|
||
public const HEADER = 1061;
|
||
public const BODY = 1062;
|
||
public const FOOTER = 1063;
|
||
|
||
/** @var array<int, array{string, string}> Layout ID => [stage3 fixture file, post type]. */
|
||
public const LAYOUTS = [
|
||
1061 => ['tb-header--globaler-header.html', 'et_header_layout'],
|
||
1062 => ['tb-body--body-mit-dynamic-content.html', 'et_body_layout'],
|
||
1063 => ['tb-footer--footer.html', 'et_footer_layout'],
|
||
];
|
||
|
||
/** @param array<string, mixed> $fields */
|
||
public static function layout(int $id, array $fields = []): WP_Post
|
||
{
|
||
return new WP_Post(array_merge([
|
||
'ID' => $id,
|
||
'post_type' => self::LAYOUTS[$id][1],
|
||
'post_status' => 'publish',
|
||
'post_title' => '[Fixture] ' . $id,
|
||
'post_content' => DiviFixtures::markup('stage3/' . self::LAYOUTS[$id][0]),
|
||
], $fields));
|
||
}
|
||
|
||
/** @param array<string, mixed> $fields */
|
||
public static function templatePost(int $id = self::TEMPLATE, array $fields = []): WP_Post
|
||
{
|
||
return new WP_Post(array_merge([
|
||
'ID' => $id,
|
||
'post_type' => DiviThemeBuilder::TEMPLATE,
|
||
'post_status' => 'publish',
|
||
'post_title' => $id === self::DEFAULT_TEMPLATE ? 'Standard-Website-Template' : '[Fixture] Vorlage Globale Instanzen',
|
||
'post_content' => '',
|
||
], $fields));
|
||
}
|
||
|
||
/**
|
||
* The template as Divi reads it (et_theme_builder_get_template); $overrides replace keys of that
|
||
* array and may set status, attached and marked_unused.
|
||
*
|
||
* @param array<string, mixed> $overrides
|
||
*/
|
||
public static function template(int $id = self::TEMPLATE, array $overrides = []): Template
|
||
{
|
||
$base = $id === self::DEFAULT_TEMPLATE
|
||
? [
|
||
'id' => $id,
|
||
'title' => 'Standard-Website-Template',
|
||
'default' => true,
|
||
'enabled' => false,
|
||
'layouts' => [
|
||
'header' => ['id' => self::HEADER, 'enabled' => true],
|
||
'body' => ['id' => 0, 'enabled' => true],
|
||
'footer' => ['id' => 0, 'enabled' => true],
|
||
],
|
||
'use_on' => [],
|
||
'exclude_from' => [],
|
||
]
|
||
: [
|
||
'id' => $id,
|
||
'title' => '[Fixture] Vorlage Globale Instanzen',
|
||
'default' => false,
|
||
'enabled' => false,
|
||
'layouts' => [
|
||
'header' => ['id' => self::HEADER, 'enabled' => true],
|
||
'body' => ['id' => self::BODY, 'enabled' => true],
|
||
'footer' => ['id' => self::FOOTER, 'enabled' => true],
|
||
],
|
||
'use_on' => ['singular:post_type:page:id:1053'],
|
||
'exclude_from' => [],
|
||
];
|
||
|
||
return Template::fromDivi(
|
||
array_merge($base, array_intersect_key($overrides, $base)),
|
||
(string) ($overrides['status'] ?? 'publish'),
|
||
(bool) ($overrides['attached'] ?? true),
|
||
isset($overrides['marked_unused']) ? (string) $overrides['marked_unused'] : null
|
||
);
|
||
}
|
||
|
||
/**
|
||
* A store whose live list is $templates, with a TB draft when $draftId > 0, $detached as templates no
|
||
* Theme Builder post lists and $byIdOnly as templates template() finds but no list names (e.g. the one a
|
||
* test lets storeTemplate() create).
|
||
*
|
||
* @param list<Template> $templates
|
||
* @param list<Template> $detached
|
||
* @param list<Template> $byIdOnly
|
||
*/
|
||
public static function store(
|
||
array $templates = [],
|
||
int $draftId = 0,
|
||
array $detached = [],
|
||
array $byIdOnly = []
|
||
): DiviThemeBuilder {
|
||
$byId = [];
|
||
foreach (array_merge($templates, $detached, $byIdOnly) as $template) {
|
||
$byId[$template->id] = $template;
|
||
}
|
||
$store = Mockery::mock(DiviThemeBuilder::class)->makePartial();
|
||
$store->allows('liveId')->andReturn(self::THEME_BUILDER);
|
||
$store->allows('draftId')->andReturn($draftId);
|
||
$store->allows('liveTemplates')->andReturn($templates);
|
||
$store->allows('templateIds')->andReturn(array_map(static fn (Template $t): int => $t->id, $templates));
|
||
$store->allows('template')->andReturnUsing(static fn (int $id): ?Template => $byId[$id] ?? null)->byDefault();
|
||
$store->allows('detachedIds')->andReturn(array_map(static fn (Template $t): int => $t->id, $detached));
|
||
$store->allows('isListedAnywhere')->andReturnUsing(
|
||
static fn (int $id): bool => in_array($id, array_map(static fn (Template $t): int => $t->id, $templates), true)
|
||
)->byDefault();
|
||
$store->allows('flatConditions')->andReturn(self::conditions());
|
||
|
||
return $store;
|
||
}
|
||
|
||
/** @param list<string> $deniedRoles Divi role options the user lacks. */
|
||
public static function access(
|
||
array $deniedRoles = [],
|
||
bool $writable = true,
|
||
?DiviThemeBuilder $store = null,
|
||
bool $visible = true
|
||
): ThemeBuilderAccess {
|
||
return new ThemeBuilderAccess(
|
||
$store ?? self::store(),
|
||
LibraryFixtures::roles($deniedRoles),
|
||
static fn (): bool => $writable,
|
||
static fn (): bool => $visible
|
||
);
|
||
}
|
||
|
||
/** A loader with the library access of LibraryFixtures and $access (default: full TB rights, empty live list). */
|
||
public static function loader(?ThemeBuilderAccess $access = null): DiviPageLoader
|
||
{
|
||
return new DiviPageLoader(DiviFixtures::schema(), new Parser(), LibraryFixtures::access(), $access ?? self::access());
|
||
}
|
||
|
||
/**
|
||
* An excerpt of et_theme_builder_get_flat_template_settings_options() as the adapter returns it
|
||
* (live DDEV dump 2026-09-27 plus the Pods type lit_person).
|
||
*
|
||
* @return array<string, array{label: string, type: ?string, value: ?string}>
|
||
*/
|
||
public static function conditions(): array
|
||
{
|
||
return [
|
||
'singular:post_type:page:all' => ['label' => 'Alle Seiten', 'type' => null, 'value' => null],
|
||
'homepage' => ['label' => 'Startseite', 'type' => null, 'value' => null],
|
||
'singular:post_type:page:id:' => ['label' => 'Bestimmte Seiten', 'type' => 'post_type', 'value' => 'page'],
|
||
'singular:post_type:page:children:id:' => [
|
||
'label' => 'Unterseiten bestimmter Seiten', 'type' => 'post_type', 'value' => 'page',
|
||
],
|
||
'singular:post_type:lit_person:all' => ['label' => 'Alle Personen', 'type' => null, 'value' => null],
|
||
'singular:post_type:lit_person:id:' => [
|
||
'label' => 'Bestimmte Personen', 'type' => 'post_type', 'value' => 'lit_person',
|
||
],
|
||
'archive:taxonomy:category:term:id:' => [
|
||
'label' => 'Bestimmte Kategorien', 'type' => 'taxonomy', 'value' => 'category',
|
||
],
|
||
'archive:user:id:' => ['label' => 'Bestimmte Autoren', 'type' => 'user', 'value' => null],
|
||
'archive:user:role:' => ['label' => 'Autoren mit Rolle', 'type' => 'user_role', 'value' => null],
|
||
'search' => ['label' => 'Suchergebnisse', 'type' => null, 'value' => null],
|
||
'404' => ['label' => '404-Seite', 'type' => null, 'value' => null],
|
||
];
|
||
}
|
||
}
|