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\Library\DiviLibraryStore;
|
||||
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\ThemeBuilder\DiviThemeBuilder;
|
||||
use Throwable;
|
||||
@@ -64,8 +65,10 @@ final class DiviCompatibility
|
||||
'Taxonomien layout_category, layout_tag (Bibliothek)' =>
|
||||
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).
|
||||
'Theme Builder: Funktionen et_theme_builder_* und et_()' =>
|
||||
static fn (): bool => (new DiviThemeBuilder())->missing() === [],
|
||||
'Theme Builder: Funktionen et_theme_builder_* und et_()' => static fn (): bool => array_filter(
|
||||
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(
|
||||
DiviThemeBuilder::postTypes(),
|
||||
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\DynamicContentMap;
|
||||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
@@ -20,6 +21,7 @@ final class DiviAdapters
|
||||
DiviPages::class,
|
||||
DiviLibraryStore::class,
|
||||
DiviRoles::class,
|
||||
DiviThemeBuilder::class,
|
||||
DiviModules::class,
|
||||
DynamicContentMap::class,
|
||||
];
|
||||
|
||||
@@ -4,6 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
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 Throwable;
|
||||
use WP_Error;
|
||||
@@ -11,26 +15,26 @@ use WP_Error;
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Adapter for Divi's Theme Builder (E7, E15; decisions note F14–F23; fixtures stage3/stage3b).
|
||||
* Reading uses Divi's own functions (theme-builder.php:378-521). Writing:
|
||||
* Adapter "Theme Builder" (spec §3; E7, E15; decisions note F14–F23; fixtures stage3/stage3b).
|
||||
* 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()
|
||||
* (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);
|
||||
* - 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
|
||||
* store_template (theme-builder.php:1545-1583) – store_template on an existing template would also
|
||||
* rewrite its default flag, title and onboarding meta.
|
||||
* et_theme_builder_api_save() (admin-ajax only, not loaded in REST requests, F20) and
|
||||
* et_theme_builder_store_template() – see the mirrors in manifest(); store_template on an existing
|
||||
* 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().
|
||||
* Not final so abilities can be tested with a mock.
|
||||
*/
|
||||
class DiviThemeBuilder
|
||||
class DiviThemeBuilder implements DiviAdapter
|
||||
{
|
||||
public const TEMPLATE = 'et_template';
|
||||
|
||||
/** Post type of the Theme Builder posts (live, draft, library) that list the templates. */
|
||||
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 = [
|
||||
'header' => 'et_header_layout',
|
||||
'body' => 'et_body_layout',
|
||||
@@ -44,18 +48,6 @@ class DiviThemeBuilder
|
||||
public const UNUSED = '_et_theme_builder_marked_as_unused';
|
||||
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 = [
|
||||
'wp_die_handler', 'wp_die_ajax_handler', 'wp_die_json_handler', 'wp_die_jsonp_handler',
|
||||
'wp_die_xml_handler', 'wp_die_xmlrpc_handler',
|
||||
@@ -83,10 +75,53 @@ class DiviThemeBuilder
|
||||
return is_string($area) ? $area : null;
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
public function missing(): array
|
||||
public static function manifest(): Manifest
|
||||
{
|
||||
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. */
|
||||
@@ -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.
|
||||
*
|
||||
* @param list<string> $useOn
|
||||
@@ -338,7 +373,7 @@ class DiviThemeBuilder
|
||||
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
|
||||
{
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
@@ -369,7 +404,7 @@ class DiviThemeBuilder
|
||||
if (! current_user_can('edit_others_posts')) {
|
||||
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 !== []) {
|
||||
throw new RuntimeException('Divi function missing: ' . implode(', ', $missing));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use LIT\MCP\Abilities\Rev;
|
||||
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
|
||||
* "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.
|
||||
@@ -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
|
||||
* 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
|
||||
* (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():
|
||||
* the last segment is the object). The object must be readable by the user (ruling P10: PostIdParser): a post
|
||||
* of that type with read_post (not trashed, no auto-draft/attachment status), a term of that taxonomy with
|
||||
* assign_term, a user only with list_users (ruling Q10, stricter than Divi), an existing role. Refusals for
|
||||
* missing and unreadable objects read the same and never name an object's title (P12). Nothing is written here.
|
||||
* plus one object segment, split the way Divi reads it back (mirror tb-load-setting-options in
|
||||
* DiviThemeBuilder::manifest(): the last segment is the object). The object must be readable by the user
|
||||
* (ruling P10: PostIdParser): a post of that type with read_post (not trashed, no auto-draft/attachment status),
|
||||
* a term of that taxonomy with assign_term, a user only with list_users (ruling Q10, stricter than Divi), an
|
||||
* 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
|
||||
* 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).
|
||||
|
||||
Reference in New Issue
Block a user