test: Divi 5 design system fixtures and format notes for Etappe 4
Seed script writes four global colors (one relative), one variable per type, four module presets (one default) and two option group presets plus the draft page "[Fixture] Designsystem" through Divi's own builder REST routes; export script saves them to tests/Fixtures/divi/stage4/. Notes document storage, write functions, side effects and permissions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exports the stage 4 design system fixtures from the local site into tests/Fixtures/divi/stage4/: global colors,
|
||||
* global variables, presets (each as stored in the database and as Divi's getter returns it), the relevant et_divi
|
||||
* subset, the (Divi 4) preset history options and the page "[Fixture] Designsystem" (post row, meta, post_content
|
||||
* byte-exact). How the data was created: tests/Integration/divi-seed-stage4-design.php and
|
||||
* tests/Fixtures/divi/stage4/README.md.
|
||||
*
|
||||
* Usage (DDEV, WordPress root):
|
||||
* wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-export-stage4-fixtures.php
|
||||
*
|
||||
* Read-only. Fixtures are committed – no personal data. No `declare(strict_types=1);` – wp eval-file uses eval().
|
||||
*/
|
||||
|
||||
defined('WP_CLI') && WP_CLI || exit;
|
||||
|
||||
use ET\Builder\Packages\GlobalData\GlobalData;
|
||||
use ET\Builder\Packages\GlobalData\GlobalPreset;
|
||||
|
||||
$lit_dir = dirname(__DIR__) . '/Fixtures/divi/stage4';
|
||||
$lit_page_title = '[Fixture] Designsystem';
|
||||
|
||||
$lit_write = static function (string $file, string $data): void {
|
||||
if (file_put_contents($file, $data) === false) {
|
||||
WP_CLI::error('Cannot write ' . $file);
|
||||
}
|
||||
};
|
||||
$lit_json = static fn ($data): string => wp_json_encode(
|
||||
$data,
|
||||
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
|
||||
) . "\n";
|
||||
|
||||
/** The option row exactly as stored (serialized string, autoload flag) – shows the PHP types Divi writes. */
|
||||
$lit_raw_option = static function (string $name): ?array {
|
||||
global $wpdb;
|
||||
$row = $wpdb->get_row($wpdb->prepare(
|
||||
"SELECT option_value, autoload FROM {$wpdb->options} WHERE option_name = %s",
|
||||
$name
|
||||
));
|
||||
|
||||
return $row === null ? null : ['option_name' => $name, 'autoload' => $row->autoload, 'option_value' => $row->option_value];
|
||||
};
|
||||
|
||||
$lit_pages = get_posts([
|
||||
'post_type' => 'page',
|
||||
'post_status' => ['draft', 'publish', 'private', 'pending'],
|
||||
'title' => $lit_page_title,
|
||||
'numberposts' => 1,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
if ($lit_pages === []) {
|
||||
WP_CLI::error('Page "' . $lit_page_title . '" not found – run tests/Integration/divi-seed-stage4-design.php first.');
|
||||
}
|
||||
$lit_page = $lit_pages[0];
|
||||
|
||||
if (! is_dir($lit_dir) && ! wp_mkdir_p($lit_dir)) {
|
||||
WP_CLI::error('Cannot create ' . $lit_dir);
|
||||
}
|
||||
|
||||
// Global colors: et_divi['et_global_data'] as stored, plus GlobalData::get_global_colors() (customizer colors merged
|
||||
// in front; their lastUpdated is generated on every read).
|
||||
$lit_et_divi = get_option('et_divi', []);
|
||||
$lit_write($lit_dir . '/global-colors.json', $lit_json([
|
||||
'stored' => [
|
||||
'option_name' => 'et_divi',
|
||||
'key' => 'et_global_data',
|
||||
'value' => maybe_unserialize($lit_et_divi['et_global_data'] ?? null),
|
||||
],
|
||||
'get_global_colors' => GlobalData::get_global_colors(),
|
||||
]));
|
||||
|
||||
// Global variables: product option et_divi_global_variables (arrays), getter result (every type is a stdClass –
|
||||
// an empty type therefore encodes as {} here, not []).
|
||||
$lit_write($lit_dir . '/global-variables.json', $lit_json([
|
||||
'stored' => get_option('et_divi_global_variables', null),
|
||||
'stored_raw' => $lit_raw_option('et_divi_global_variables'),
|
||||
'get_global_variables' => GlobalData::get_global_variables(),
|
||||
'get_global_variables_php_types' => array_map(
|
||||
static fn ($items) => get_debug_type($items),
|
||||
GlobalData::get_global_variables()
|
||||
),
|
||||
]));
|
||||
|
||||
// Presets: product option et_divi_builder_global_presets_d5, plus the D4 legacy option and import flag.
|
||||
$lit_write($lit_dir . '/presets.json', $lit_json([
|
||||
'option_name' => 'et_divi_' . GlobalPreset::option_name(),
|
||||
'stored' => get_option('et_divi_' . GlobalPreset::option_name(), null),
|
||||
'get_data' => GlobalPreset::get_data(),
|
||||
'legacy_d4_option' => $lit_raw_option('et_divi_builder_global_presets_ng'),
|
||||
'is_legacy_presets_imported' => GlobalPreset::is_legacy_presets_imported(),
|
||||
]));
|
||||
|
||||
// Preset history (Divi 4 storage; Divi 5 preset saves do not write it).
|
||||
global $wpdb;
|
||||
$lit_history_rows = $wpdb->get_results(
|
||||
"SELECT option_name, option_value, autoload FROM {$wpdb->options}
|
||||
WHERE option_name LIKE 'et\\_divi\\_builder\\_presets\\_history%'
|
||||
OR option_name LIKE 'et\\_divi\\_builder\\_global\\_presets\\_history%'
|
||||
OR option_name LIKE 'et\\_divi\\_builder\\_custom\\_defaults\\_history%'
|
||||
ORDER BY option_name"
|
||||
);
|
||||
$lit_write($lit_dir . '/presets-history.json', $lit_json([
|
||||
'rows' => array_map(static fn ($row) => [
|
||||
'option_name' => $row->option_name,
|
||||
'autoload' => $row->autoload,
|
||||
'value' => maybe_unserialize($row->option_value),
|
||||
], $lit_history_rows),
|
||||
'et_divi_flags' => array_intersect_key((array) $lit_et_divi, array_flip([
|
||||
'builder_global_presets_history_migrated',
|
||||
'presets_storage_migrated_flag',
|
||||
'custom_defaults_migrated_flag',
|
||||
])),
|
||||
]));
|
||||
|
||||
// Theme options relevant to spec §7 (whitelist): present keys with their stored value, absent keys listed.
|
||||
$lit_keys = [
|
||||
'heading_font', 'body_font', 'heading_font_weight', 'body_font_weight', 'primary_nav_font', 'secondary_nav_font',
|
||||
'all_buttons_font', 'body_font_size', 'tablet_body_font_size', 'phone_body_font_size', 'body_header_size',
|
||||
'tablet_header_font_size', 'phone_header_font_size', 'body_font_height', 'body_header_height',
|
||||
'body_header_spacing', 'body_header_style', 'content_width', 'gutter_width', 'section_padding', 'row_padding',
|
||||
'accent_color', 'secondary_accent_color', 'header_color', 'font_color', 'link_color', 'divi_logo',
|
||||
];
|
||||
foreach (array_keys((array) $lit_et_divi) as $key) {
|
||||
if (str_starts_with($key, 'all_buttons_') || str_starts_with($key, 'divi_seo_')) {
|
||||
$lit_keys[] = $key;
|
||||
}
|
||||
}
|
||||
$lit_keys = array_values(array_unique($lit_keys));
|
||||
$lit_present = [];
|
||||
$lit_absent = [];
|
||||
foreach ($lit_keys as $key) {
|
||||
if (array_key_exists($key, (array) $lit_et_divi)) {
|
||||
$lit_present[$key] = $lit_et_divi[$key];
|
||||
} else {
|
||||
$lit_absent[] = $key;
|
||||
}
|
||||
}
|
||||
$lit_write($lit_dir . '/et-divi-subset.json', $lit_json([
|
||||
'option_name' => 'et_divi',
|
||||
'present' => (object) $lit_present,
|
||||
'absent' => $lit_absent,
|
||||
'effective' => [
|
||||
'accent_colors' => GlobalData::get_accent_color_values(),
|
||||
'customizer_fonts' => GlobalData::get_customizer_fonts(),
|
||||
],
|
||||
]));
|
||||
|
||||
// The page: post row, all meta (raw), post_content byte-exact.
|
||||
$lit_meta = [];
|
||||
foreach ($wpdb->get_results($wpdb->prepare(
|
||||
"SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d ORDER BY meta_id",
|
||||
$lit_page->ID
|
||||
)) as $row) {
|
||||
if (! in_array($row->meta_key, ['_edit_lock', '_edit_last'], true)) {
|
||||
$lit_meta[$row->meta_key][] = $row->meta_value;
|
||||
}
|
||||
}
|
||||
$lit_write($lit_dir . '/page--designsystem.json', $lit_json([
|
||||
'post' => [
|
||||
'ID' => $lit_page->ID,
|
||||
'post_type' => $lit_page->post_type,
|
||||
'post_status' => $lit_page->post_status,
|
||||
'post_title' => $lit_page->post_title,
|
||||
'post_name' => $lit_page->post_name,
|
||||
'post_date_gmt' => $lit_page->post_date_gmt,
|
||||
'post_modified_gmt' => $lit_page->post_modified_gmt,
|
||||
'content_file' => 'page--designsystem.html',
|
||||
'content_sha256' => hash('sha256', $lit_page->post_content),
|
||||
],
|
||||
'meta' => (object) $lit_meta,
|
||||
]));
|
||||
$lit_write($lit_dir . '/page--designsystem.html', $lit_page->post_content);
|
||||
|
||||
$lit_write($lit_dir . '/manifest.json', $lit_json([
|
||||
'divi_version' => wp_get_theme('Divi')->get('Version'),
|
||||
'builder_version' => ET_BUILDER_VERSION,
|
||||
'files' => [
|
||||
'global-colors.json',
|
||||
'global-variables.json',
|
||||
'presets.json',
|
||||
'presets-history.json',
|
||||
'et-divi-subset.json',
|
||||
'page--designsystem.json',
|
||||
'page--designsystem.html',
|
||||
],
|
||||
'posts' => ['page--designsystem' => $lit_page->ID],
|
||||
]));
|
||||
|
||||
WP_CLI::success('Stage 4 fixtures exported to ' . $lit_dir);
|
||||
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Seeds realistic Divi 5 design data for the Etappe 4 fixtures (spec §15 "Format der Presets/Variablen an echten
|
||||
* Daten verifizieren"): four global colors (one relative to another), one global variable per type, four module
|
||||
* presets (one of them the default of its module) and two option group presets that reference colors and variables,
|
||||
* plus the draft page "[Fixture] Designsystem" that uses them.
|
||||
*
|
||||
* Everything is written through the REST endpoints the Visual Builder itself calls, dispatched in-process with
|
||||
* rest_do_request() – so Divi's own argument schema, sanitize callbacks, permission callbacks and save functions run:
|
||||
* POST divi/v1/global-data/global-colors → GlobalDataController::update_global_colors → GlobalData::set_global_colors
|
||||
* POST divi/v1/global-data/global-variables → GlobalDataController::update_global_variables → GlobalData::set_global_variables
|
||||
* POST divi/v1/global-data/global-preset/sync → GlobalPresetController::sync → GlobalPreset::prepare_data/save_data
|
||||
* POST divi/v1/sync-to-server → SyncToServerController::update (like "Entwurf speichern")
|
||||
* Formats and sources: docs/superpowers/notes/2026-09-28-etappe-4-divi-formate.md.
|
||||
*
|
||||
* Usage (DDEV, WordPress root):
|
||||
* wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-seed-stage4-design.php
|
||||
*
|
||||
* Idempotent: entries are found again by their "[Fixture] …" label/name and updated in place (IDs, "created" and
|
||||
* "lastUpdated" stay when nothing changed); nothing is duplicated. It never sends the five customizer colors
|
||||
* (gcid-primary-color …) – their values in et_divi stay untouched – and never deletes anything.
|
||||
* Runs as the first administrator when WP-CLI has no --user. No `declare(strict_types=1);` – wp eval-file uses eval().
|
||||
*/
|
||||
|
||||
defined('WP_CLI') && WP_CLI || exit;
|
||||
|
||||
use ET\Builder\Framework\Controllers\RESTController;
|
||||
use ET\Builder\Packages\Conversion\Conversion;
|
||||
use ET\Builder\Packages\GlobalData\GlobalData;
|
||||
use ET\Builder\Packages\GlobalData\GlobalPreset;
|
||||
|
||||
if (get_current_user_id() === 0) {
|
||||
$lit_admins = get_users(['role' => 'administrator', 'number' => 1, 'orderby' => 'ID']);
|
||||
if ($lit_admins === []) {
|
||||
WP_CLI::error('No administrator found.');
|
||||
}
|
||||
wp_set_current_user($lit_admins[0]->ID);
|
||||
}
|
||||
if (! current_user_can('edit_theme_options') || ! et_pb_is_allowed('variables_manager')) {
|
||||
WP_CLI::error('The current user needs edit_theme_options and Divi\'s role right "variables_manager".');
|
||||
}
|
||||
|
||||
/** Random ID like the Visual Builder's generateNanoUuid(): 10 characters of [0-9a-z]. */
|
||||
$lit_nano = static function (): string {
|
||||
$alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
$id = '';
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$id .= $alphabet[random_int(0, 35)];
|
||||
}
|
||||
|
||||
return $id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispatches a request to one of Divi's REST routes as the current user; stops on any error. Divi checks a per-route
|
||||
* nonce in the X-ET-Nonce header (RESTRoute, action "divi/v1<route>--POST"), created here like the builder gets it.
|
||||
*/
|
||||
$lit_rest = static function (string $route, array $body): array {
|
||||
$request = new WP_REST_Request('POST', '/divi/v1' . $route);
|
||||
$request->set_header('Content-Type', 'application/json');
|
||||
$request->set_header('X-ET-Nonce', RESTController::create_nonce('divi/v1', $route, 'POST'));
|
||||
$request->set_body(wp_json_encode($body));
|
||||
$response = rest_do_request($request);
|
||||
$data = rest_get_server()->response_to_data($response, false);
|
||||
if ($response->is_error() || $response->get_status() >= 300) {
|
||||
WP_CLI::error(sprintf('%s → HTTP %d: %s', $route, $response->get_status(), wp_json_encode($data)));
|
||||
}
|
||||
|
||||
return is_array($data) ? $data : (array) $data;
|
||||
};
|
||||
|
||||
/** Dynamic reference to a global color, optionally with HSL/opacity adjustments (relative color). */
|
||||
$lit_color_ref = static fn (string $id, array $settings = []): string => '$variable(' . wp_json_encode(
|
||||
['type' => 'color', 'value' => ['name' => $id, 'settings' => (object) $settings]]
|
||||
) . ')$';
|
||||
|
||||
/** Dynamic reference to a global variable (numbers, strings, images, links, fonts). */
|
||||
$lit_var_ref = static fn (string $id): string => '$variable(' . wp_json_encode(
|
||||
['type' => 'content', 'value' => ['name' => $id, 'settings' => (object) []]]
|
||||
) . ')$';
|
||||
|
||||
$lit_now_iso = gmdate('Y-m-d\TH:i:s.v\Z');
|
||||
$lit_now_ms = (int) round(microtime(true) * 1000);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 1. Global colors
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
$lit_colors_before = GlobalData::get_global_colors();
|
||||
$lit_color_id = static function (string $label) use ($lit_colors_before, $lit_nano): string {
|
||||
foreach ($lit_colors_before as $id => $color) {
|
||||
if (is_array($color) && ($color['label'] ?? '') === $label && ! isset(GlobalData::$customizer_colors[$id])) {
|
||||
return (string) $id;
|
||||
}
|
||||
}
|
||||
|
||||
return 'gcid-' . $lit_nano();
|
||||
};
|
||||
|
||||
$lit_c = [
|
||||
'holz_dunkel' => $lit_color_id('[Fixture] Holz dunkel'),
|
||||
'holz_hell' => $lit_color_id('[Fixture] Holz hell'),
|
||||
'salbei' => $lit_color_id('[Fixture] Salbeigrün'),
|
||||
'sand' => $lit_color_id('[Fixture] Sandbeige'),
|
||||
];
|
||||
$lit_color_specs = [
|
||||
$lit_c['holz_dunkel'] => ['[Fixture] Holz dunkel', '#5b3a29'],
|
||||
// Relative color: Holz dunkel, 30 % lighter – stored as a $variable() reference with HSL settings.
|
||||
$lit_c['holz_hell'] => ['[Fixture] Holz hell', $lit_color_ref($lit_c['holz_dunkel'], ['lightness' => 30])],
|
||||
$lit_c['salbei'] => ['[Fixture] Salbeigrün', '#7d9b76'],
|
||||
$lit_c['sand'] => ['[Fixture] Sandbeige', '#efe6d8'],
|
||||
];
|
||||
|
||||
$lit_colors_payload = [];
|
||||
foreach ($lit_color_specs as $id => [$label, $value]) {
|
||||
$old = $lit_colors_before[$id] ?? null;
|
||||
$unchanged = is_array($old) && ($old['color'] ?? null) === $value && ($old['status'] ?? '') === 'active';
|
||||
$lit_colors_payload[$id] = [
|
||||
'color' => $value,
|
||||
'folder' => '',
|
||||
'label' => $label,
|
||||
'lastUpdated' => $unchanged ? (string) $old['lastUpdated'] : $lit_now_iso,
|
||||
'status' => 'active',
|
||||
'usedInPosts' => is_array($old['usedInPosts'] ?? null) ? $old['usedInPosts'] : [],
|
||||
];
|
||||
}
|
||||
// Only the fixture colors are sent: the endpoint merges (set_global_colors(…, true)), and leaving out the customizer
|
||||
// colors keeps accent_color & co. in et_divi untouched.
|
||||
$lit_rest('/global-data/global-colors', ['global_colors' => $lit_colors_payload]);
|
||||
WP_CLI::log('Colors: ' . implode(', ', array_map(
|
||||
static fn ($id, $spec) => $spec[0] . ' = ' . $id,
|
||||
array_keys($lit_color_specs),
|
||||
$lit_color_specs
|
||||
)));
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 2. Global variables – the endpoint REPLACES the whole option, so the full current set is sent.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
$lit_vars = [];
|
||||
foreach (GlobalData::get_global_variables() as $type => $items) {
|
||||
if (! in_array($type, ['numbers', 'strings', 'images', 'links', 'fonts'], true)) {
|
||||
continue; // "colors" is never stored in global_variables (sanitize_global_variables_data drops it).
|
||||
}
|
||||
foreach ((array) $items as $id => $item) {
|
||||
if ($type === 'fonts' && isset(GlobalData::$customizer_fonts[$id])) {
|
||||
continue; // Customizer fonts come from et_divi heading_font/body_font, not from this option.
|
||||
}
|
||||
unset($item['allowedActions']);
|
||||
$lit_vars[$type][$id] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$lit_var_id = static function (string $type, string $label) use (&$lit_vars, $lit_nano): string {
|
||||
foreach ($lit_vars[$type] ?? [] as $id => $item) {
|
||||
if (($item['label'] ?? '') === $label) {
|
||||
return (string) $id;
|
||||
}
|
||||
}
|
||||
|
||||
return 'gvid-' . $lit_nano();
|
||||
};
|
||||
|
||||
$lit_image = get_posts(['post_type' => 'attachment', 'post_mime_type' => 'image', 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC']);
|
||||
$lit_image_url = $lit_image !== [] ? (string) wp_get_attachment_url($lit_image[0]->ID) : get_template_directory_uri() . '/images/logo.png';
|
||||
|
||||
$lit_var_specs = [
|
||||
'numbers' => [
|
||||
'eckenradius' => ['[Fixture] Eckenradius', '8px'],
|
||||
'abstand' => ['[Fixture] Abstand Karte', 'clamp(1.5rem, 4vw, 2.5rem)'],
|
||||
],
|
||||
'strings' => ['slogan' => ['[Fixture] Slogan', 'Handwerk aus Meisterhand – seit 1987']],
|
||||
'images' => ['werkstatt' => ['[Fixture] Werkstattbild', $lit_image_url]],
|
||||
'links' => ['kontakt' => ['[Fixture] Kontaktseite', home_url('/kontakt/')]],
|
||||
'fonts' => ['akzent' => ['[Fixture] Akzentschrift', 'Merriweather']],
|
||||
];
|
||||
|
||||
$lit_v = [];
|
||||
foreach ($lit_var_specs as $type => $specs) {
|
||||
foreach ($specs as $key => [$label, $value]) {
|
||||
$id = $lit_var_id($type, $label);
|
||||
$lit_v[$key] = $id;
|
||||
$old = $lit_vars[$type][$id] ?? null;
|
||||
$unchanged = is_array($old) && (string) ($old['value'] ?? '') === $value && ($old['status'] ?? '') === 'active';
|
||||
$order = is_array($old) && isset($old['order'])
|
||||
? (int) $old['order']
|
||||
: 1 + count(array_filter($lit_vars[$type] ?? [], static fn ($i) => ($i['status'] ?? '') === 'active'));
|
||||
$lit_vars[$type][$id] = [
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'value' => $value,
|
||||
'order' => $order,
|
||||
'status' => 'active',
|
||||
'lastUpdated' => $unchanged && isset($old['lastUpdated']) ? (string) $old['lastUpdated'] : $lit_now_iso,
|
||||
'variableType' => $type,
|
||||
];
|
||||
}
|
||||
}
|
||||
$lit_rest('/global-data/global-variables', ['global_variables' => $lit_vars]);
|
||||
WP_CLI::log('Variables: ' . wp_json_encode($lit_v));
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 3. Presets – the sync endpoint REPLACES the whole option; it refuses a payload with fewer presets than stored.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
$lit_presets = GlobalPreset::get_data();
|
||||
$lit_find_preset = static function (string $kind, string $sub, string $name) use ($lit_presets): ?array {
|
||||
foreach ($lit_presets[$kind][$sub]['items'] ?? [] as $item) {
|
||||
if (is_array($item) && ($item['name'] ?? '') === $name) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Splits preset attrs into styleAttrs / renderAttrs with Divi's own attribute map (Conversion, D4→D5 preset path). */
|
||||
$lit_split = static function (string $moduleName, array $attrs): array {
|
||||
$map = Conversion::get_preset_attrs_mapping($moduleName);
|
||||
|
||||
return [
|
||||
Conversion::get_preset_attrs($attrs, ['style'], $map),
|
||||
Conversion::get_preset_attrs($attrs, ['html', 'script'], $map),
|
||||
];
|
||||
};
|
||||
|
||||
/** Builds one preset item like the Visual Builder's generateNewPreset / generateNewOptionGroupPreset. */
|
||||
$lit_preset = static function (string $kind, string $sub, array $spec) use ($lit_find_preset, $lit_split, $lit_nano, $lit_now_ms): array {
|
||||
$old = $lit_find_preset($kind, $sub, $spec['name']);
|
||||
[$styleAttrs, $renderAttrs] = $lit_split($spec['moduleName'], $spec['attrs']);
|
||||
$item = ['type' => $kind, 'id' => $old['id'] ?? $lit_nano(), 'name' => $spec['name']];
|
||||
if ($kind === 'group') {
|
||||
$item += ['groupName' => $sub, 'groupId' => $spec['groupId']];
|
||||
}
|
||||
$item += [
|
||||
'moduleName' => $spec['moduleName'],
|
||||
'version' => ET_BUILDER_VERSION,
|
||||
'created' => (int) ($old['created'] ?? $lit_now_ms),
|
||||
'updated' => (int) ($old['updated'] ?? $lit_now_ms),
|
||||
'attrs' => $spec['attrs'],
|
||||
];
|
||||
if ($styleAttrs !== []) {
|
||||
$item['styleAttrs'] = $styleAttrs;
|
||||
}
|
||||
if ($renderAttrs !== []) {
|
||||
$item['renderAttrs'] = $renderAttrs;
|
||||
}
|
||||
if (! empty($spec['groupPresets'])) {
|
||||
$item['groupPresets'] = $spec['groupPresets'];
|
||||
}
|
||||
if ($old !== null) {
|
||||
$compare = static function (array $preset): string {
|
||||
unset($preset['updated'], $preset['priority'], $preset['order']);
|
||||
ksort($preset);
|
||||
|
||||
return wp_json_encode($preset);
|
||||
};
|
||||
if ($compare($item) !== $compare($old)) {
|
||||
$item['updated'] = $lit_now_ms;
|
||||
}
|
||||
foreach (['priority', 'order'] as $key) {
|
||||
if (isset($old[$key])) {
|
||||
$item[$key] = $old[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
};
|
||||
|
||||
$lit_radius = static fn (string $value): array => [
|
||||
'sync' => 'on',
|
||||
'topLeft' => $value,
|
||||
'topRight' => $value,
|
||||
'bottomLeft' => $value,
|
||||
'bottomRight' => $value,
|
||||
];
|
||||
$lit_padding = static fn (string $value): array => [
|
||||
'top' => $value,
|
||||
'right' => $value,
|
||||
'bottom' => $value,
|
||||
'left' => $value,
|
||||
'syncVertical' => 'on',
|
||||
'syncHorizontal' => 'on',
|
||||
];
|
||||
|
||||
// Option group presets first – the text module preset nests the body-font preset.
|
||||
$lit_group_font = $lit_preset('group', 'divi/font-body', [
|
||||
'name' => '[Fixture] Fließtext',
|
||||
'moduleName' => 'divi/text',
|
||||
'groupId' => 'content.decoration.bodyFont',
|
||||
'attrs' => ['content' => ['decoration' => ['bodyFont' => ['body' => ['font' => ['desktop' => ['value' => [
|
||||
'family' => $lit_var_ref($lit_v['akzent']),
|
||||
'color' => $lit_color_ref($lit_c['holz_dunkel']),
|
||||
'size' => '17px',
|
||||
'lineHeight' => '1.8em',
|
||||
]]]]]]]],
|
||||
]);
|
||||
$lit_group_spacing = $lit_preset('group', 'divi/spacing', [
|
||||
'name' => '[Fixture] Abstand Karte',
|
||||
'moduleName' => 'divi/blurb',
|
||||
'groupId' => 'module.decoration.spacing',
|
||||
'attrs' => ['module' => ['decoration' => ['spacing' => ['desktop' => ['value' => [
|
||||
'padding' => $lit_padding($lit_var_ref($lit_v['abstand'])),
|
||||
'margin' => ['bottom' => '24px'],
|
||||
]]]]]],
|
||||
]);
|
||||
|
||||
$lit_module_button = $lit_preset('module', 'divi/button', [
|
||||
'name' => '[Fixture] Button Primär',
|
||||
'moduleName' => 'divi/button',
|
||||
'attrs' => ['button' => ['decoration' => [
|
||||
'background' => ['desktop' => ['value' => ['color' => $lit_color_ref($lit_c['holz_dunkel'])]]],
|
||||
'border' => ['desktop' => ['value' => ['radius' => $lit_radius($lit_var_ref($lit_v['eckenradius']))]]],
|
||||
'font' => ['font' => ['desktop' => ['value' => [
|
||||
'color' => $lit_color_ref($lit_c['sand']),
|
||||
'weight' => '600',
|
||||
]]]],
|
||||
]]],
|
||||
]);
|
||||
$lit_module_text = $lit_preset('module', 'divi/text', [
|
||||
'name' => '[Fixture] Text Hinweis',
|
||||
'moduleName' => 'divi/text',
|
||||
'attrs' => ['module' => ['decoration' => [
|
||||
'background' => ['desktop' => ['value' => ['color' => $lit_color_ref($lit_c['sand'])]]],
|
||||
'border' => ['desktop' => ['value' => ['radius' => $lit_radius($lit_var_ref($lit_v['eckenradius']))]]],
|
||||
'spacing' => ['desktop' => ['value' => ['padding' => $lit_padding($lit_var_ref($lit_v['abstand']))]]],
|
||||
]]],
|
||||
'groupPresets' => [
|
||||
'content.decoration.bodyFont' => ['presetId' => [$lit_group_font['id']], 'groupName' => 'divi/font-body'],
|
||||
],
|
||||
]);
|
||||
$lit_module_blurb = $lit_preset('module', 'divi/blurb', [
|
||||
'name' => '[Fixture] Blurb Leistung',
|
||||
'moduleName' => 'divi/blurb',
|
||||
'attrs' => [
|
||||
'title' => ['decoration' => ['font' => ['font' => ['desktop' => ['value' => [
|
||||
'family' => $lit_var_ref($lit_v['akzent']),
|
||||
'color' => $lit_color_ref($lit_c['holz_dunkel']),
|
||||
'weight' => '700',
|
||||
]]]]]],
|
||||
'module' => ['decoration' => ['border' => ['desktop' => ['value' => [
|
||||
'radius' => $lit_radius($lit_var_ref($lit_v['eckenradius'])),
|
||||
'styles' => ['all' => ['width' => '2px', 'color' => $lit_color_ref($lit_c['salbei']), 'style' => 'solid']],
|
||||
]]]]],
|
||||
],
|
||||
]);
|
||||
// Default preset of divi/divider – a module no other page of this site uses, so the default changes nothing else.
|
||||
$lit_module_divider = $lit_preset('module', 'divi/divider', [
|
||||
'name' => '[Fixture] Trenner Standard',
|
||||
'moduleName' => 'divi/divider',
|
||||
'attrs' => ['divider' => ['advanced' => ['line' => ['desktop' => ['value' => [
|
||||
'color' => $lit_color_ref($lit_c['holz_hell']),
|
||||
'weight' => '3px',
|
||||
]]]]]],
|
||||
]);
|
||||
|
||||
$lit_mine = [
|
||||
'module' => [
|
||||
'divi/button' => [$lit_module_button],
|
||||
'divi/text' => [$lit_module_text],
|
||||
'divi/blurb' => [$lit_module_blurb],
|
||||
'divi/divider' => [$lit_module_divider],
|
||||
],
|
||||
'group' => [
|
||||
'divi/font-body' => [$lit_group_font],
|
||||
'divi/spacing' => [$lit_group_spacing],
|
||||
],
|
||||
];
|
||||
$lit_defaults = ['module' => ['divi/divider' => $lit_module_divider['id']], 'group' => []];
|
||||
|
||||
// Stored format (option) → REST schema format (list of {default, items: list}), merged with our items.
|
||||
$lit_payload = ['module' => [], 'group' => []];
|
||||
foreach (['module', 'group'] as $kind) {
|
||||
$subs = array_unique(array_merge(array_keys($lit_presets[$kind] ?? []), array_keys($lit_mine[$kind])));
|
||||
foreach ($subs as $sub) {
|
||||
$items = $lit_presets[$kind][$sub]['items'] ?? [];
|
||||
foreach ($lit_mine[$kind][$sub] ?? [] as $item) {
|
||||
$items[$item['id']] = $item;
|
||||
}
|
||||
$lit_payload[$kind][] = [
|
||||
'default' => (string) ($lit_defaults[$kind][$sub] ?? ($lit_presets[$kind][$sub]['default'] ?? '')),
|
||||
'items' => array_values($items),
|
||||
];
|
||||
}
|
||||
}
|
||||
$lit_rest('/global-data/global-preset/sync', ['presets' => $lit_payload, 'actionType' => 'ADD_NEW_MODULE_PRESET']);
|
||||
WP_CLI::log('Presets: ' . wp_json_encode([
|
||||
'button' => $lit_module_button['id'],
|
||||
'text' => $lit_module_text['id'],
|
||||
'blurb' => $lit_module_blurb['id'],
|
||||
'divider (default)' => $lit_module_divider['id'],
|
||||
'font-body' => $lit_group_font['id'],
|
||||
'spacing' => $lit_group_spacing['id'],
|
||||
]));
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 4. Page "[Fixture] Designsystem" (draft) with preset and variable references, saved like the builder's draft save.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
$lit_block = static function (string $name, array $attrs, array $inner = []): string {
|
||||
$attrs['builderVersion'] = ET_BUILDER_VERSION;
|
||||
$json = serialize_block_attributes($attrs);
|
||||
if ($inner === []) {
|
||||
return "<!-- wp:{$name} {$json} /-->\n";
|
||||
}
|
||||
|
||||
return "<!-- wp:{$name} {$json} -->\n" . implode('', $inner) . "<!-- /wp:{$name} -->\n";
|
||||
};
|
||||
$lit_label = static fn (string $label): array => ['meta' => ['adminLabel' => ['desktop' => ['value' => $label]]]];
|
||||
$lit_text = static fn (string $html): array => ['innerContent' => ['desktop' => ['value' => $html]]];
|
||||
|
||||
$lit_modules = [
|
||||
$lit_block('divi/heading', [
|
||||
'module' => $lit_label('Überschrift'),
|
||||
'title' => ['innerContent' => ['desktop' => ['value' => 'Designsystem-Fixture']]],
|
||||
]),
|
||||
$lit_block('divi/text', [
|
||||
'module' => $lit_label('Text mit Modul-Preset'),
|
||||
'modulePreset' => [$lit_module_text['id']],
|
||||
'content' => $lit_text('<p>Hinweis-Text mit dem Modul-Preset „[Fixture] Text Hinweis“ (enthält das Gruppen-Preset „[Fixture] Fließtext“).</p>'),
|
||||
]),
|
||||
$lit_block('divi/text', [
|
||||
'module' => $lit_label('Text mit Gruppen-Preset'),
|
||||
'groupPreset' => ['content.decoration.bodyFont' => ['presetId' => [$lit_group_font['id']], 'groupName' => 'divi/font-body']],
|
||||
'content' => $lit_text('<p>Fließtext mit dem Gruppen-Preset „[Fixture] Fließtext“ direkt am Modul.</p>'),
|
||||
]),
|
||||
$lit_block('divi/text', [
|
||||
'module' => $lit_label('Text mit String-Variable'),
|
||||
'content' => $lit_text($lit_var_ref($lit_v['slogan'])),
|
||||
]),
|
||||
$lit_block('divi/button', [
|
||||
'module' => $lit_label('Button mit Modul-Preset'),
|
||||
'modulePreset' => [$lit_module_button['id']],
|
||||
'button' => ['innerContent' => ['desktop' => ['value' => [
|
||||
'text' => 'Angebot anfragen',
|
||||
'linkUrl' => $lit_var_ref($lit_v['kontakt']),
|
||||
]]]],
|
||||
]),
|
||||
$lit_block('divi/blurb', [
|
||||
'module' => $lit_label('Blurb mit Modul- und Gruppen-Preset'),
|
||||
'modulePreset' => [$lit_module_blurb['id']],
|
||||
'groupPreset' => ['module.decoration.spacing' => ['presetId' => [$lit_group_spacing['id']], 'groupName' => 'divi/spacing']],
|
||||
'title' => $lit_text('Möbel nach Maß'),
|
||||
'content' => $lit_text('<p>Blurb mit „[Fixture] Blurb Leistung“ und dem Gruppen-Preset „[Fixture] Abstand Karte“.</p>'),
|
||||
]),
|
||||
$lit_block('divi/image', [
|
||||
'module' => $lit_label('Bild aus Bild-Variable'),
|
||||
'image' => ['innerContent' => ['desktop' => ['value' => [
|
||||
'src' => $lit_var_ref($lit_v['werkstatt']),
|
||||
'alt' => 'Werkstatt',
|
||||
]]]],
|
||||
]),
|
||||
$lit_block('divi/divider', [
|
||||
'module' => $lit_label('Trenner ohne Preset (Standard-Preset)'),
|
||||
]),
|
||||
];
|
||||
$lit_column = $lit_block('divi/column', [
|
||||
'module' => ['decoration' => ['sizing' => ['desktop' => ['value' => ['flexType' => '24_24']]]]],
|
||||
], $lit_modules);
|
||||
$lit_row = $lit_block('divi/row', ['module' => $lit_label('Designsystem Zeile')], [$lit_column]);
|
||||
$lit_section = $lit_block('divi/section', ['module' => $lit_label('Designsystem Abschnitt')], [$lit_row]);
|
||||
$lit_content = '<!-- wp:divi/placeholder -->' . rtrim($lit_section, "\n") . '<!-- /wp:divi/placeholder -->';
|
||||
|
||||
$lit_page_title = '[Fixture] Designsystem';
|
||||
$lit_existing = get_posts([
|
||||
'post_type' => 'page',
|
||||
'post_status' => ['draft', 'publish', 'private', 'pending'],
|
||||
'title' => $lit_page_title,
|
||||
'numberposts' => 1,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
if ($lit_existing !== []) {
|
||||
$lit_page_id = $lit_existing[0]->ID;
|
||||
} else {
|
||||
$lit_page_id = wp_insert_post([
|
||||
'post_type' => 'page',
|
||||
'post_status' => 'draft',
|
||||
'post_title' => $lit_page_title,
|
||||
'post_content' => '',
|
||||
], true);
|
||||
if (is_wp_error($lit_page_id)) {
|
||||
WP_CLI::error($lit_page_id->get_error_message());
|
||||
}
|
||||
et_builder_enable_for_post($lit_page_id);
|
||||
}
|
||||
if (get_post_field('post_content', $lit_page_id) !== $lit_content) {
|
||||
$lit_rest('/sync-to-server', [
|
||||
'post_id' => $lit_page_id,
|
||||
'post_status' => get_post_status($lit_page_id),
|
||||
'syncType' => 'draft',
|
||||
'content' => ['post_content' => $lit_content],
|
||||
]);
|
||||
}
|
||||
WP_CLI::log('Page: ' . $lit_page_id);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
// 5. Read back through Divi and through the plugin's DesignSystemReader.
|
||||
// ---------------------------------------------------------------------------------------------------------------
|
||||
$lit_colors_after = GlobalData::get_global_colors();
|
||||
foreach ($lit_color_specs as $id => [$label, $value]) {
|
||||
if (($lit_colors_after[$id]['color'] ?? null) !== $value || ($lit_colors_after[$id]['label'] ?? null) !== $label) {
|
||||
WP_CLI::error("Color {$id} did not read back.");
|
||||
}
|
||||
}
|
||||
$lit_vars_after = GlobalData::get_global_variables();
|
||||
foreach ($lit_var_specs as $type => $specs) {
|
||||
foreach ($specs as $key => [$label, $value]) {
|
||||
$item = ((array) ($lit_vars_after[$type] ?? []))[$lit_v[$key]] ?? null;
|
||||
if (! is_array($item) || $item['value'] !== $value || $item['label'] !== $label) {
|
||||
WP_CLI::error("Variable {$lit_v[$key]} ({$type}) did not read back.");
|
||||
}
|
||||
}
|
||||
}
|
||||
$lit_presets_after = GlobalPreset::get_data();
|
||||
foreach ($lit_mine as $kind => $subs) {
|
||||
foreach ($subs as $sub => $items) {
|
||||
foreach ($items as $item) {
|
||||
if (($lit_presets_after[$kind][$sub]['items'][$item['id']]['name'] ?? null) !== $item['name']) {
|
||||
WP_CLI::error("Preset {$item['id']} ({$kind} {$sub}) did not read back.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($lit_presets_after['module']['divi/divider']['default'] ?? '') !== $lit_module_divider['id']) {
|
||||
WP_CLI::error('The divider default preset did not read back.');
|
||||
}
|
||||
|
||||
if (class_exists(\LIT\MCP\Integrations\Divi\Design\DesignSystemReader::class)) {
|
||||
$lit_ds = (new \LIT\MCP\Integrations\Divi\Design\DesignSystemReader())->read();
|
||||
$lit_labels = array_column($lit_ds->colors(), 'label');
|
||||
$lit_var_labels = array_column($lit_ds->variables(), 'label');
|
||||
$lit_preset_names = array_values($lit_ds->presetNames());
|
||||
foreach (array_column($lit_color_specs, 0) as $label) {
|
||||
in_array($label, $lit_labels, true) || WP_CLI::error("DesignSystemReader misses color {$label}.");
|
||||
}
|
||||
foreach ($lit_var_specs as $specs) {
|
||||
foreach ($specs as [$label]) {
|
||||
in_array($label, $lit_var_labels, true) || WP_CLI::error("DesignSystemReader misses variable {$label}.");
|
||||
}
|
||||
}
|
||||
foreach ($lit_mine as $subs) {
|
||||
foreach ($subs as $items) {
|
||||
foreach ($items as $item) {
|
||||
in_array($item['name'], $lit_preset_names, true) || WP_CLI::error("DesignSystemReader misses preset {$item['name']}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
WP_CLI::log(sprintf(
|
||||
'DesignSystemReader: %d colors, %d variables, %d presets, fonts %s',
|
||||
count($lit_ds->colors()),
|
||||
count($lit_ds->variables()),
|
||||
count($lit_ds->presets()),
|
||||
wp_json_encode($lit_ds->fonts())
|
||||
));
|
||||
} else {
|
||||
WP_CLI::warning('DesignSystemReader not loaded (plugin inactive?) – skipped.');
|
||||
}
|
||||
|
||||
WP_CLI::success('Stage 4 design data seeded; page ' . $lit_page_id . '.');
|
||||
Reference in New Issue
Block a user