Files
WP-LIT-MCP/tests/Support/ThemeBuilderFixtures.php
T
janniclas.loosenandClaude Opus 5.5 d04a508751 feat: Theme Builder adapter, template value and per-object access
DiviThemeBuilder wraps Divi's Theme Builder: reads through Divi's functions,
writes layouts/templates through insert_layout/store_template behind a
wp_die guard (edit_others_posts and function checks first, every handler
filter and output buffer restored in finally), list/conditions/enabled
mirrored from Divi's own save. Template: live = attached and enabled,
whatever the post status (ruling Q8/B8). ThemeBuilderAccess: edit_others_posts
plus Divi's theme_builder role, hidden IDs answer like missing ones,
canEditTemplate (B11) and layoutsRefusal (B10). DiviCompatibility checks the
Theme Builder functions and post types (E15).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 15:18:42 +02:00

178 lines
7.4 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\Tests\Support;
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);
$store->allows('detachedIds')->andReturn(array_map(static fn (Template $t): int => $t->id, $detached));
$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
);
}
/**
* 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],
];
}
}