'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--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 "\n"; } return "\n" . implode('', $inner) . "\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('

Hinweis-Text mit dem Modul-Preset „[Fixture] Text Hinweis“ (enthält das Gruppen-Preset „[Fixture] Fließtext“).

'), ]), $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('

Fließtext mit dem Gruppen-Preset „[Fixture] Fließtext“ direkt am Modul.

'), ]), $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('

Blurb mit „[Fixture] Blurb Leistung“ und dem Gruppen-Preset „[Fixture] Abstand Karte“.

'), ]), $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 = '' . rtrim($lit_section, "\n") . ''; $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 . '.');