refactor: Theme Builder adapter manifest with three mirrors
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi;
|
|||||||
use LIT\MCP\Integrations\Divi\BlockTree\GlobalInstance;
|
use LIT\MCP\Integrations\Divi\BlockTree\GlobalInstance;
|
||||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@@ -64,8 +65,10 @@ final class DiviCompatibility
|
|||||||
'Taxonomien layout_category, layout_tag (Bibliothek)' =>
|
'Taxonomien layout_category, layout_tag (Bibliothek)' =>
|
||||||
static fn (): bool => taxonomy_exists('layout_category') && taxonomy_exists('layout_tag'),
|
static fn (): bool => taxonomy_exists('layout_category') && taxonomy_exists('layout_tag'),
|
||||||
// Includes et_theme_builder_clear_wp_cache (Theme Builder cache of every site-wide clear, review M-2).
|
// Includes et_theme_builder_clear_wp_cache (Theme Builder cache of every site-wide clear, review M-2).
|
||||||
'Theme Builder: Funktionen et_theme_builder_* und et_()' =>
|
'Theme Builder: Funktionen et_theme_builder_* und et_()' => static fn (): bool => array_filter(
|
||||||
static fn (): bool => (new DiviThemeBuilder())->missing() === [],
|
DiviThemeBuilder::manifest()->calls,
|
||||||
|
static fn (Call $call): bool => $call->className() === null && ! function_exists($call->member())
|
||||||
|
) === [],
|
||||||
'Theme Builder: Post-Typen et_template und Layouts' => static fn (): bool => array_filter(
|
'Theme Builder: Post-Typen et_template und Layouts' => static fn (): bool => array_filter(
|
||||||
DiviThemeBuilder::postTypes(),
|
DiviThemeBuilder::postTypes(),
|
||||||
static fn (string $type): bool => ! post_type_exists($type)
|
static fn (string $type): bool => ! post_type_exists($type)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
|||||||
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||||
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ final class DiviAdapters
|
|||||||
DiviPages::class,
|
DiviPages::class,
|
||||||
DiviLibraryStore::class,
|
DiviLibraryStore::class,
|
||||||
DiviRoles::class,
|
DiviRoles::class,
|
||||||
|
DiviThemeBuilder::class,
|
||||||
DiviModules::class,
|
DiviModules::class,
|
||||||
DynamicContentMap::class,
|
DynamicContentMap::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Integrations\Divi\ThemeBuilder;
|
namespace LIT\MCP\Integrations\Divi\ThemeBuilder;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
@@ -11,26 +15,26 @@ use WP_Error;
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter for Divi's Theme Builder (E7, E15; decisions note F14–F23; fixtures stage3/stage3b).
|
* Adapter "Theme Builder" (spec §3; E7, E15; decisions note F14–F23; fixtures stage3/stage3b).
|
||||||
* Reading uses Divi's own functions (theme-builder.php:378-521). Writing:
|
* Reading uses Divi's own functions (theme-builder.php). Writing:
|
||||||
* - layouts and new templates through et_theme_builder_insert_layout() and et_theme_builder_store_template()
|
* - layouts and new templates through et_theme_builder_insert_layout() and et_theme_builder_store_template()
|
||||||
* (theme-builder.php:1463-1667). Both wp_die() without edit_others_posts: the right is checked first,
|
* (theme-builder.php). Both wp_die() without edit_others_posts: the right is checked first,
|
||||||
* and while they run every wp_die handler throws instead (guarded(), defense in depth);
|
* and while they run every wp_die handler throws instead (guarded(), defense in depth);
|
||||||
* - the live Theme Builder post's template list, conditions and the enabled flag are mirrored from
|
* - the live Theme Builder post's template list, conditions and the enabled flag are mirrored from
|
||||||
* et_theme_builder_api_save() (api.php:223-243; admin-ajax only, not loaded in REST requests, F20) and
|
* et_theme_builder_api_save() (admin-ajax only, not loaded in REST requests, F20) and
|
||||||
* store_template (theme-builder.php:1545-1583) – store_template on an existing template would also
|
* et_theme_builder_store_template() – see the mirrors in manifest(); store_template on an existing
|
||||||
* rewrite its default flag, title and onboarding meta.
|
* template would also rewrite its default flag, title and onboarding meta.
|
||||||
* Never touched: the Theme Builder draft (auto-draft) and et_theme_builder_trash_draft_and_unused_posts().
|
* Never touched: the Theme Builder draft (auto-draft) and et_theme_builder_trash_draft_and_unused_posts().
|
||||||
* Not final so abilities can be tested with a mock.
|
* Not final so abilities can be tested with a mock.
|
||||||
*/
|
*/
|
||||||
class DiviThemeBuilder
|
class DiviThemeBuilder implements DiviAdapter
|
||||||
{
|
{
|
||||||
public const TEMPLATE = 'et_template';
|
public const TEMPLATE = 'et_template';
|
||||||
|
|
||||||
/** Post type of the Theme Builder posts (live, draft, library) that list the templates. */
|
/** Post type of the Theme Builder posts (live, draft, library) that list the templates. */
|
||||||
public const THEME_BUILDER = 'et_theme_builder';
|
public const THEME_BUILDER = 'et_theme_builder';
|
||||||
|
|
||||||
/** Area => layout post type (theme-builder.php:7-23). */
|
/** Area => layout post type (theme-builder.php). */
|
||||||
public const LAYOUT_TYPES = [
|
public const LAYOUT_TYPES = [
|
||||||
'header' => 'et_header_layout',
|
'header' => 'et_header_layout',
|
||||||
'body' => 'et_body_layout',
|
'body' => 'et_body_layout',
|
||||||
@@ -44,18 +48,6 @@ class DiviThemeBuilder
|
|||||||
public const UNUSED = '_et_theme_builder_marked_as_unused';
|
public const UNUSED = '_et_theme_builder_marked_as_unused';
|
||||||
public const BACKUP_OPTION = 'et_tb_templates_backup_';
|
public const BACKUP_OPTION = 'et_tb_templates_backup_';
|
||||||
|
|
||||||
/** Divi functions this adapter calls; DiviCompatibility reports missing ones. */
|
|
||||||
public const FUNCTIONS = [
|
|
||||||
'et_theme_builder_get_theme_builder_post_id',
|
|
||||||
'et_theme_builder_get_theme_builder_template_ids',
|
|
||||||
'et_theme_builder_get_template',
|
|
||||||
'et_theme_builder_insert_layout',
|
|
||||||
'et_theme_builder_store_template',
|
|
||||||
'et_theme_builder_get_flat_template_settings_options',
|
|
||||||
'et_theme_builder_clear_wp_cache',
|
|
||||||
'et_',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const DIE_FILTERS = [
|
private const DIE_FILTERS = [
|
||||||
'wp_die_handler', 'wp_die_ajax_handler', 'wp_die_json_handler', 'wp_die_jsonp_handler',
|
'wp_die_handler', 'wp_die_ajax_handler', 'wp_die_json_handler', 'wp_die_jsonp_handler',
|
||||||
'wp_die_xml_handler', 'wp_die_xmlrpc_handler',
|
'wp_die_xml_handler', 'wp_die_xmlrpc_handler',
|
||||||
@@ -83,10 +75,53 @@ class DiviThemeBuilder
|
|||||||
return is_string($area) ? $area : null;
|
return is_string($area) ? $area : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return list<string> */
|
public static function manifest(): Manifest
|
||||||
public function missing(): array
|
|
||||||
{
|
{
|
||||||
return array_values(array_filter(self::FUNCTIONS, static fn (string $name): bool => ! function_exists($name)));
|
$dir = 'includes/builder/frontend-builder/theme-builder/';
|
||||||
|
|
||||||
|
return new Manifest(
|
||||||
|
Manifest::THEME_BUILDER,
|
||||||
|
calls: [
|
||||||
|
new Call('et_theme_builder_get_theme_builder_post_id', 2),
|
||||||
|
new Call('et_theme_builder_get_theme_builder_template_ids', 2),
|
||||||
|
new Call('et_theme_builder_get_template', 1),
|
||||||
|
new Call('et_theme_builder_insert_layout', 1),
|
||||||
|
new Call('et_theme_builder_store_template', 3),
|
||||||
|
new Call('et_theme_builder_get_flat_template_settings_options', 0),
|
||||||
|
new Call('et_theme_builder_clear_wp_cache', 1),
|
||||||
|
// store_template needs Divi's et_() helper (E15).
|
||||||
|
new Call('et_'),
|
||||||
|
new Call('ET_Core_PageResource::remove_static_resources', 3),
|
||||||
|
],
|
||||||
|
postTypes: self::postTypes(),
|
||||||
|
keys: [
|
||||||
|
self::TEMPLATE_LIST, self::USE_ON, self::EXCLUDE_FROM, self::ENABLED, self::UNUSED, self::BACKUP_OPTION,
|
||||||
|
],
|
||||||
|
mirrors: [
|
||||||
|
new Mirror(
|
||||||
|
'tb-api-save',
|
||||||
|
$dir . 'api.php',
|
||||||
|
'et_theme_builder_api_save',
|
||||||
|
null,
|
||||||
|
'Vorlagenliste des Live-Theme-Builders (_et_template mit Sicherung et_tb_templates_backup_) und '
|
||||||
|
. '„ungenutzt“-Markierung'
|
||||||
|
),
|
||||||
|
new Mirror(
|
||||||
|
'tb-store-template',
|
||||||
|
$dir . 'theme-builder.php',
|
||||||
|
'et_theme_builder_store_template',
|
||||||
|
null,
|
||||||
|
'Bedingungen (_et_use_on, _et_exclude_from) und Aktiv-Schalter (_et_enabled) einer Vorlage'
|
||||||
|
),
|
||||||
|
new Mirror(
|
||||||
|
'tb-load-setting-options',
|
||||||
|
$dir . 'theme-builder.php',
|
||||||
|
'et_theme_builder_load_template_setting_options',
|
||||||
|
null,
|
||||||
|
'Zerlegen der Bedingungs-IDs (TemplateConditions)'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The live Theme Builder post (publish, no library TB); $create makes it like Divi's first save. 0 = none. */
|
/** The live Theme Builder post (publish, no library TB); $create makes it like Divi's first save. 0 = none. */
|
||||||
@@ -275,7 +310,7 @@ class DiviThemeBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces both condition lists like store_template (theme-builder.php:1565-1583): delete, then one
|
* Replaces both condition lists like store_template (theme-builder.php): delete, then one
|
||||||
* row per unique condition.
|
* row per unique condition.
|
||||||
*
|
*
|
||||||
* @param list<string> $useOn
|
* @param list<string> $useOn
|
||||||
@@ -338,7 +373,7 @@ class DiviThemeBuilder
|
|||||||
return $flat;
|
return $flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Divi's Theme Builder cache and every static CSS file (spec §5.6 step 5; api.php:249-254). */
|
/** Divi's Theme Builder cache and every static CSS file (spec §5.6 step 5; api.php). */
|
||||||
public function clearCaches(): void
|
public function clearCaches(): void
|
||||||
{
|
{
|
||||||
self::clearSiteCaches();
|
self::clearSiteCaches();
|
||||||
@@ -360,7 +395,7 @@ class DiviThemeBuilder
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Before any Divi write function runs: the functions exist (E15 – no fatal error halfway) and the user
|
* Before any Divi write function runs: the functions exist (E15 – no fatal error halfway) and the user
|
||||||
* holds edit_others_posts, without which Divi calls wp_die() (theme-builder.php:1464, 1644).
|
* holds edit_others_posts, without which Divi calls wp_die() (theme-builder.php).
|
||||||
*
|
*
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
*/
|
*/
|
||||||
@@ -369,7 +404,7 @@ class DiviThemeBuilder
|
|||||||
if (! current_user_can('edit_others_posts')) {
|
if (! current_user_can('edit_others_posts')) {
|
||||||
throw new RuntimeException('edit_others_posts missing – Divi would call wp_die()');
|
throw new RuntimeException('edit_others_posts missing – Divi would call wp_die()');
|
||||||
}
|
}
|
||||||
$missing = array_values(array_intersect($functions, $this->missing()));
|
$missing = array_values(array_filter($functions, static fn (string $name): bool => ! function_exists($name)));
|
||||||
if ($missing !== []) {
|
if ($missing !== []) {
|
||||||
throw new RuntimeException('Divi function missing: ' . implode(', ', $missing));
|
throw new RuntimeException('Divi function missing: ' . implode(', ', $missing));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use LIT\MCP\Abilities\Rev;
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Theme Builder template as Divi reads it (et_theme_builder_get_template(), theme-builder.php:471-521;
|
* A Theme Builder template as Divi reads it (et_theme_builder_get_template(), theme-builder.php;
|
||||||
* fixtures stage3/stage3b), plus whether the live Theme Builder post lists it (attached) and Divi's
|
* fixtures stage3/stage3b), plus whether the live Theme Builder post lists it (attached) and Divi's
|
||||||
* "unused" mark (set when a Theme Builder save drops it from the list, stage3b README finding 10).
|
* "unused" mark (set when a Theme Builder save drops it from the list, stage3b README finding 10).
|
||||||
* rev() is template_rev, the lock of the Theme Builder tools. No WordPress calls.
|
* rev() is template_rev, the lock of the Theme Builder tools. No WordPress calls.
|
||||||
@@ -238,7 +238,7 @@ final class Template
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The page families a condition covers (Divi's IDs, theme-builder.php:689-1060): singular:post_type:<type>,
|
* The page families a condition covers (Divi's IDs, theme-builder.php): singular:post_type:<type>,
|
||||||
* archive:taxonomy:<taxonomy>, archive:post_type:<type>, archive:user, archive:date; "<kind>:all" and posts
|
* archive:taxonomy:<taxonomy>, archive:post_type:<type>, archive:user, archive:date; "<kind>:all" and posts
|
||||||
* in a term (singular:taxonomy:…) the whole kind; the front page may be a page or the posts archive.
|
* in a term (singular:taxonomy:…) the whole kind; the front page may be a page or the posts archive.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ defined('ABSPATH') || exit;
|
|||||||
/**
|
/**
|
||||||
* Theme Builder conditions (_et_use_on/_et_exclude_from, F21, E7): only IDs from Divi's own list
|
* Theme Builder conditions (_et_use_on/_et_exclude_from, F21, E7): only IDs from Divi's own list
|
||||||
* (et_theme_builder_get_flat_template_settings_options()) – exactly, or for an entry ending in ":" that entry
|
* (et_theme_builder_get_flat_template_settings_options()) – exactly, or for an entry ending in ":" that entry
|
||||||
* plus one object segment, split the way Divi reads it back (et_theme_builder_load_template_setting_options():
|
* plus one object segment, split the way Divi reads it back (mirror tb-load-setting-options in
|
||||||
* the last segment is the object). The object must be readable by the user (ruling P10: PostIdParser): a post
|
* DiviThemeBuilder::manifest(): the last segment is the object). The object must be readable by the user
|
||||||
* of that type with read_post (not trashed, no auto-draft/attachment status), a term of that taxonomy with
|
* (ruling P10: PostIdParser): a post of that type with read_post (not trashed, no auto-draft/attachment status),
|
||||||
* assign_term, a user only with list_users (ruling Q10, stricter than Divi), an existing role. Refusals for
|
* a term of that taxonomy with assign_term, a user only with list_users (ruling Q10, stricter than Divi), an
|
||||||
* missing and unreadable objects read the same and never name an object's title (P12). Nothing is written here.
|
* existing role. Refusals for missing and unreadable objects read the same and never name an object's title
|
||||||
|
* (P12). Nothing is written here.
|
||||||
* Every condition ID is limited to letters, digits, "_", ":" and "-" (at most 200 characters): a role slug with
|
* Every condition ID is limited to letters, digits, "_", ":" and "-" (at most 200 characters): a role slug with
|
||||||
* other characters (possible with role plugins) is refused as an invalid condition ID and must be set in Divi's
|
* other characters (possible with role plugins) is refused as an invalid condition ID and must be set in Divi's
|
||||||
* Theme Builder (review M-6).
|
* Theme Builder (review M-6).
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ final class DiviBoundaryTest extends TestCase
|
|||||||
private const LEGACY = [
|
private const LEGACY = [
|
||||||
'Integrations/Divi/Design/DesignSystemReader.php',
|
'Integrations/Divi/Design/DesignSystemReader.php',
|
||||||
'Integrations/Divi/DiviCompatibility.php',
|
'Integrations/Divi/DiviCompatibility.php',
|
||||||
'Integrations/Divi/ThemeBuilder/DiviThemeBuilder.php',
|
|
||||||
'Integrations/Media/MediaUsageFinder.php',
|
'Integrations/Media/MediaUsageFinder.php',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\ThemeBuilder;
|
|||||||
use Brain\Monkey\Actions;
|
use Brain\Monkey\Actions;
|
||||||
use Brain\Monkey\Filters;
|
use Brain\Monkey\Filters;
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\InsertedBeforeFailure;
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\InsertedBeforeFailure;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
@@ -17,6 +20,23 @@ use WP_Post;
|
|||||||
|
|
||||||
final class DiviThemeBuilderTest extends TestCase
|
final class DiviThemeBuilderTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testIsTheRegisteredThemeBuilderAdapterWithItsMirrors(): void
|
||||||
|
{
|
||||||
|
$manifest = DiviThemeBuilder::manifest();
|
||||||
|
|
||||||
|
self::assertContains(DiviThemeBuilder::class, DiviAdapters::CLASSES);
|
||||||
|
self::assertSame(Manifest::THEME_BUILDER, $manifest->area);
|
||||||
|
self::assertSame(DiviThemeBuilder::postTypes(), $manifest->postTypes);
|
||||||
|
self::assertSame(
|
||||||
|
['tb-api-save', 'tb-store-template', 'tb-load-setting-options'],
|
||||||
|
array_map(static fn ($mirror): string => $mirror->id, $manifest->mirrors)
|
||||||
|
);
|
||||||
|
self::assertContains('et_theme_builder_store_template()', array_map(
|
||||||
|
static fn (Call $call): string => $call->label(),
|
||||||
|
$manifest->calls
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public function testAreaOfTheLayoutTypes(): void
|
public function testAreaOfTheLayoutTypes(): void
|
||||||
{
|
{
|
||||||
self::assertSame('header', DiviThemeBuilder::areaOf('et_header_layout'));
|
self::assertSame('header', DiviThemeBuilder::areaOf('et_header_layout'));
|
||||||
@@ -383,26 +403,28 @@ final class DiviThemeBuilderTest extends TestCase
|
|||||||
(new DiviThemeBuilder())->insertLayout('sidebar', 'Seite');
|
(new DiviThemeBuilder())->insertLayout('sidebar', 'Seite');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** E15: a Divi function that moved is reported, never called (no fatal error mid-write). */
|
/**
|
||||||
|
* E15: a Divi function that moved is reported, never called (no fatal error mid-write). Own process:
|
||||||
|
* no other test may have stubbed these names yet, or function_exists() would report them as present
|
||||||
|
* for the rest of the run (Brain Monkey defines a stubbed function process-wide).
|
||||||
|
*
|
||||||
|
* @runInSeparateProcess
|
||||||
|
*/
|
||||||
public function testMissingDiviFunctionsAreRefusedBeforeWriting(): void
|
public function testMissingDiviFunctionsAreRefusedBeforeWriting(): void
|
||||||
{
|
{
|
||||||
Functions\when('current_user_can')->justReturn(true);
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
Functions\expect('et_theme_builder_insert_layout')->never();
|
|
||||||
Functions\expect('et_theme_builder_store_template')->never();
|
|
||||||
$store = Mockery::mock(DiviThemeBuilder::class)->makePartial();
|
|
||||||
$store->allows('missing')->andReturn(['et_theme_builder_insert_layout', 'et_']);
|
|
||||||
|
|
||||||
foreach ([
|
foreach ([
|
||||||
static fn () => $store->insertLayout('body', 'Person – Inhalt'),
|
static fn (DiviThemeBuilder $store) => $store->insertLayout('body', 'Person – Inhalt'),
|
||||||
static fn () => $store->storeTemplate(1064, [
|
static fn (DiviThemeBuilder $store) => $store->storeTemplate(1064, [
|
||||||
'title' => 'Person', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
|
'title' => 'Person', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
|
||||||
]),
|
]),
|
||||||
] as $call) {
|
] as $call) {
|
||||||
try {
|
try {
|
||||||
$call();
|
$call(new DiviThemeBuilder());
|
||||||
self::fail('Divi was called although it is missing');
|
self::fail('Divi was called although it is missing');
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
self::assertStringContainsString('missing', $e->getMessage());
|
self::assertStringContainsString('Divi function missing', $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user