Files
WP-LIT-MCP/docs/superpowers/notes/2026-09-28-etappe-4-divi-formate.md
T
janniclas.loosenandClaude Opus 5.5 2e4309cb42 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
2026-09-30 06:03:54 +02:00

199 lines
24 KiB
Markdown
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.
# Etappe 4 – Datenformate des Divi-Designsystems (Divi 5.2.1)
Stand 2026-09-28. Grundlage: Quelltext von Divi 5.2.1 und echte Daten, die über Divis eigene Speicherwege angelegt wurden (Seed-Skript `tests/Integration/divi-seed-stage4-design.php`, Fixtures `tests/Fixtures/divi/stage4/`). Erfüllt Spec §14 („Datenformate Designsystem nur aus Code abgeleitet“) und §15 („Format der Presets/Variablen an echten Daten verifizieren“).
Pfadangaben relativ zu `wp-content/themes/Divi/`. Abkürzung **B5** = `includes/builder-5/server/`. Minifizierte Builder-Dateien liegen in `includes/builder-5/visual-builder/build/`; dort steht statt einer Zeile ein Suchbegriff.
## 0. Grundlagen: wo Divi speichert
`et_get_option()`/`et_update_option()` (`epanel/custom_functions.php:211`, `:272`) haben drei Ziele:
- normale Theme-Option → Schlüssel im Array der Option **`et_divi`** (autoload);
- „Product Setting“ (letzter Parameter `true`) → **eigene Option `et_divi_<name>`**, autoload aus (`:224`, `:288-292`);
- `et_delete_option()` (`:316`) kennt **keine** Product Settings und löscht immer nur den Schlüssel in `et_divi`.
Folge: `GlobalPreset::delete_data()` (B5 `Packages/GlobalData/GlobalPreset.php:149-158`) ruft `et_delete_option(option_name())` auf und entfernt die Option `et_divi_builder_global_presets_d5` **nicht** – nur den statischen Cache. Für Etappe 4 nicht verwenden.
`et_update_option()` aktualisiert zusätzlich das globale `$et_theme_options`; direktes `update_option('et_divi', …)` lässt diesen Cache im selben Request veralten.
## 1. Globale Farben
**Speicherort:** `et_divi['et_global_data']['global_colors']` (normale Theme-Option, kein Product Setting). Die fünf Customizer-Farben liegen **nicht** dort, sondern als `et_divi['accent_color']`, `secondary_accent_color`, `header_color`, `font_color`, `link_color` (Zuordnung `GlobalData::$customizer_colors`, B5 `Packages/GlobalData/GlobalData.php:58-83`).
**Format eines Eintrags** (Schlüssel = ID):
```json
"gcid-q9swzy97df": {
"color": "#5b3a29",
"folder": "",
"label": "[Fixture] Holz dunkel",
"lastUpdated": "2026-09-27T22:21:39.000Z",
"status": "active",
"usedInPosts": []
}
```
- ID: `gcid-` + 10 Zeichen `[0-9a-z]` (Builder: `generateVariableId`/`generateNanoUuid`, `global-data.js`, Suche ``S=(e,t)=>e||("colors"===t?`gcid-${_()}``). Ältere Daten können UUIDs haben (`gcid-79d0acb1-…`, Beispiele `GlobalData.php:700-770`). Prüfung serverseitig nur `substr($id, 0, 5) === 'gcid-'` (`:597-603`).
- `status`: `active` | `inactive` | `temporary`. Der Builder **löscht nie**: „Löschen“ setzt `status: inactive` (`global-data.js`, Suche `case"DELETE_GLOBAL_COLOR"`).
- `label`/`folder` werden gespeichert, obwohl der Konverter-Kommentar „not until D6“ sagt (`:159-160`); der Builder liest `label` (Suche `label:t.label||""`).
- `lastUpdated`: Format `Y-m-d\TH:i:s.v\Z`.
- **Relative Farbe** (Farbe aus anderer Farbe): `color` enthält eine Referenz
`$variable({"type":"color","value":{"name":"gcid-q9swzy97df","settings":{"lightness":30}}})$`
mit `settings` aus `hue`, `saturation`, `lightness`, `opacity` (`GlobalData::transform_state_into_global_color_value`, `:181-206`). CSS-Ausgabe: `--gcid-989stx15zc: hsl(from var(--gcid-q9swzy97df) calc(h + 0) calc(s + 0) calc(l + 30));` (`FrontEnd/Module/Style.php:910-947` über `StyleLibrary/Utils/Utils.php:86-130`). Abhängigkeiten werden rekursiv aufgelöst (`GlobalData.php:220-310`, Tiefe max. 10 in `:1336ff`).
**Lesen:** `GlobalData::get_global_colors()` (`:333-354`) liefert Customizer-Farben (neu erzeugt, `folder: customizer`, übersetztes Label, `lastUpdated` = jetzt, `:370-398`) **vor** den gespeicherten Farben; gleichnamige gespeicherte Customizer-IDs werden verworfen. Ergebnis wird statisch gecacht (`$_cached_data`), `set_global_colors()` setzt den Cache zurück, `get_customizer_colors()` hat einen eigenen `static`-Cache ohne Reset.
**Schreiben:** `GlobalData::set_global_colors(array $data, $already_sanitized = false)` (`:650-691`):
1. ohne `$already_sanitized`: `sanitize_global_colors_data()` (`:585-616`) – verwirft Einträge ohne `color` oder ohne `gcid-`, wendet `sanitize_text_field` auf jeden skalaren Wert an; `usedInPosts` wird mit `array_map` behandelt und **muss ein Array sein** (sonst `TypeError`).
2. Customizer-IDs im Payload → `et_update_option(<option_name>, color)` und aus dem Payload entfernt (`:664-674`).
3. **`$already_sanitized = false` ersetzt alle gespeicherten Farben, `true` mischt** (`array_merge`) mit den vorhandenen (`:678-684`).
4. `et_update_option('et_global_data', …)`, dann `ET_Core_PageResource::remove_static_resources('all', 'all', true)` (`:687-690`).
**Builder-Speicherweg:** `POST divi/v1/global-data/global-colors` (`VisualBuilder/REST/RESTRegistration.php:1001-1008`) → Schema-Sanitize `sanitize_global_colors_data` (`GlobalDataController.php:100-108`) → `set_global_colors($colors, true)` (`:71-78`), also **Mischen**. Der Builder schickt dabei die komplette Farbliste **einschließlich der fünf Customizer-Farben** (`global-data.js`, Suche `restRoute:"/divi/v1/global-data/global-colors"`); Entfernen einer Farbe ist über diesen Weg nicht möglich.
**Seiteneffekte:**
- Customizer-Sync ist direkt eingebaut (Schritt 2); ein separater Hook existiert nur für Divi 4 (`includes/builder/functions.php:13205-13237`, `et_global_colors_saved`, mit veralteten IDs `gcid-header-color`/`gcid-font-color`).
- `usedInPosts` pflegt nur der Builder im Browser (`UPDATE_GLOBAL_COLORS_USED_IN_POSTS`); serverseitig wird es nie berechnet. Über PHP angelegte Farben behalten `[]`.
- Einmalige Migration Divi 4 → 5 (`maybe_convert_global_colors_data`, `:526-551`) läuft beim Laden der Builder-Einstellungen (`Framework/Settings/Settings.php:159-162`).
**Rechte:** REST-Route nur `current_user_can('edit_posts')` (`GlobalDataController.php:124-130`); `set_global_colors()` selbst prüft nichts. Zusätzlich verlangt jede Divi-REST-Route den Nonce `X-ET-Nonce` für die Aktion `/divi/v1/<route>--POST` (`Framework/Route/RESTRoute.php:98`, `Framework/Controllers/RESTController.php:158-178`).
## 2. Globale Variablen
**Speicherort:** Product Setting **`et_divi_global_variables`** (eigene Option, autoload aus), gespeichert mit `et_update_option('global_variables', $data, false, '', '', true)` (`GlobalData.php:1016`).
**Gespeichertes Format** (PHP-Arrays):
```json
{
"numbers": { "gvid-f6yq6mayfy": { "id": "gvid-f6yq6mayfy", "label": "[Fixture] Eckenradius", "value": "8px",
"order": "1", "status": "active", "lastUpdated": "…", "variableType": "numbers" } },
"strings": { … }, "images": { … }, "links": { … }, "fonts": { … }
}
```
- Typen: nur `numbers`, `strings`, `images`, `links`, `fonts` (`sanitize_global_variables_data`, `:804-850`). **`colors` wird beim Speichern verworfen** – „Farb-Variablen“ im Variablen-Manager sind die globalen Farben aus Abschnitt 1.
- ID: `gvid-` + 10 Zeichen `[0-9a-z]`; Pflicht: `gvid-`-Präfix und nicht-leeres `label`.
- **Das Feld `id` im Eintrag ist nötig:** die CSS-Ausgabe benutzt `$value['id']`, nicht den Schlüssel (`FrontEnd/Module/Style.php:1002`).
- Sanitize: `value` bei `strings` zeichengetreu (`wp_check_invalid_utf8` + `wp_kses_no_null`), bei `links` `esc_url_raw`, sonst `sanitize_text_field` – dadurch wird `order` zum String (`"1"`). `allowedActions` wird verworfen. Der Builder speichert außerdem `lastUpdated` und `variableType` mit (`global-data.js`, Suche `case"ADD_GLOBAL_VARIABLE"`).
- `status`: `active` | `archived`. Builder-„Löschen“ setzt `archived` und `order: null` (Suche `case"DELETE_GLOBAL_VARIABLE"`), echtes Entfernen nur ohne Label/Wert.
- `numbers` erlaubt beliebige CSS-Ausdrücke (`clamp(1.5rem, 4vw, 2.5rem)` bleibt erhalten).
**Lesen:** `GlobalData::get_global_variables()` (`:896-963`) liefert ein Array, **jede Gruppe als `stdClass`** (`(object) $items`, `:959-962`), die Einträge darin als Arrays; ergänzt `allowedActions` und stellt die Customizer-Schriften `--et_global_heading_font` / `--et_global_body_font` (aus `et_divi['heading_font']`/`['body_font']`, Standard „Open Sans“, `:93-104`, `:415-437`) an den Anfang von `fonts`. Leere Gruppen sind leere `stdClass` (JSON `{}`). Eine beschädigte `fonts`-Gruppe wird still auf `[]` gesetzt (`:925`).
**Schreiben:** `GlobalData::set_global_variables(array $data)` (`:1002-1020`) **ersetzt die komplette Option**. Vorher also vollständig lesen, `stdClass` → Array wandeln, `allowedActions` und die beiden Customizer-Schriften herausnehmen (sonst landen sie in der Option), dann schreiben. `import_global_variables()` (`:1087-1137`) mischt, arbeitet aber mit `stdClass`-Gruppen und schreibt nur `id/label/value/status`.
Danach `remove_static_resources('all', 'all', true)`.
**Rechte:** `set_global_variables()` prüft `current_user_can('edit_theme_options') && et_pb_is_allowed('variables_manager')` und **kehrt sonst ohne Fehler zurück** (`:1003-1005`). Die REST-Route prüft nur `edit_posts` (`GlobalDataController.php:350-356`) und meldet danach Erfolg mit den unveränderten Daten – ein stiller Fehlschlag. `variables_manager` ist ein Recht des Divi-Rollen-Editors (`includes/builder/functions.php:9219-9222`, Auswertung `includes/builder/core.php:1128ff`, Option `et_pb_role_settings`; ohne Eintrag gilt „an“).
**Referenzen in Modulen und Presets:** `$variable({"type":"content","value":{"name":"gvid-…","settings":{}}})$` – für Zahlen/Schriften wird daraus `var(--gvid-…)` (`StyleLibrary/Utils/Utils.php:86-130`), für Strings/Links/Bilder der Wert beim Rendern (Dynamic Content, `Packages/Module/Layout/Components/DynamicContent/DynamicContentGlobalVariableOptions.php`). `:root` enthält nur `numbers` und `fonts` (`Style.php:991-1030`), Schriftwerte in Anführungszeichen. In `post_content` ist `"` innerhalb der Referenz als `"` kodiert (WordPress-Block-Serialisierung).
**Schriften der Customizer-Seite** schreibt der Builder separat: `POST divi/v1/global-data/global-fonts` → `et_update_option('heading_font'|'body_font')` + Cache leeren (`GlobalDataController.php:166-190`, Recht `edit_posts`).
## 3. Presets (Modul- und Optionsgruppen-Presets)
**Speicherort:** Product Setting **`et_divi_builder_global_presets_d5`** (`GlobalPreset::option_name()` = `builder_global_presets_d5`, `GlobalPreset.php:129`), autoload aus. Divi-4-Presets: `et_divi_builder_global_presets_ng` (wird bei Bedarf importiert, Flag `builder_is_legacy_presets_imported_to_d5`).
**Gespeichertes Format:**
```json
{
"module": {
"divi/button": { "default": "", "items": { "3gbmxlurey": { …Modul-Preset… } } },
"divi/divider": { "default": "g7kvfq5uh8", "items": { "g7kvfq5uh8": { … } } }
},
"group": {
"divi/font-body": { "default": "", "items": { "ja6o54jtlc": { …Gruppen-Preset… } } }
}
}
```
Modul-Preset-Eintrag: `type: "module"`, `id`, `name`, `moduleName`, `version` (Builder-Version, hier `"5.2.1"`), `created`/`updated` (**Millisekunden**, Builder `Date.now()`; PHP-Hilfen `_generate_new_preset` nutzen `time()` in Sekunden, `:1695-1707`), `attrs`, optional `styleAttrs`, `renderAttrs`, `groupPresets`, `priority`, `order`.
Gruppen-Preset-Eintrag zusätzlich `groupName` (z. B. `divi/font-body`), `groupId` (Attributpfad im Quellmodul, z. B. `content.decoration.bodyFont`), `moduleName` (Quellmodul), optional `primaryAttrName` (nur über JS-Filter gesetzt, `module-utils.js`, Suche `divi.optionGroupPresetPrimaryAttrNameResolver`).
- **IDs** 10 Zeichen `[0-9a-z]`, ohne Präfix (Builder `generateDefaultPresetName`, `global-data.js`). Namen sind **nicht eindeutig** – der Builder zählt „Button 1, Button 2 …“.
- **`attrs`** = normale Modulattribute (Pfad wie im Block, z. B. `button.decoration.background.desktop.value.color`); Gruppen-Presets tragen die **vollen Pfade des Quellmoduls** und werden beim Anwenden auf andere Module umgerechnet (`GlobalPresetItemGroup::_attrs_resolver`, `GlobalPresetItemGroup.php:245ff`, `GlobalPresetItemGroupAttrNameResolver`).
- **`styleAttrs`/`renderAttrs`**: Teilmengen von `attrs` für CSS bzw. HTML/Skript. Serverseitig berechenbar mit `Conversion::get_preset_attrs($attrs, ['style'] | ['html','script'], Conversion::get_preset_attrs_mapping($moduleName))` (B5 `Packages/Conversion/Conversion.php:2242`, `:2479`; genutzt in `convert_preset`, `:2144-2145`). Das Frontend benutzt `renderAttrs`/`styleAttrs` der Gruppen-Presets direkt (`Packages/ModuleLibrary/ModuleRegistration.php:326-340`).
- **`groupPresets`** im Modul-Preset (verschachtelte Gruppen-Presets): `{ "<groupId>": { "presetId": ["<id>"], "groupName": "divi/…" } }`.
- **`default`** je Modul/Gruppe: ID des Standard-Presets oder `""`. Der Builder legt beim Öffnen des Preset-Managers leere Standard-Presets an (`ADD_DEFAULT_MODULE_PRESET`); ein Standard-Preset **ohne `attrs`** erzeugt keine Klasse (`get_module_preset_class_names`, `GlobalPreset.php:730-800`).
- **`priority`** fehlt meist und gilt dann als `10` (`:676`, `:2078`, `:2761`); das REST-Schema-Default `10` (`GlobalPresetController.php`, `preset_type_properties`) wird für verschachtelte Einträge nicht eingesetzt.
**Verweise in Blöcken** (`post_content`):
- Modul-Preset: `"modulePreset": ["3gbmxlurey"]` – **Array** (Stapel); ein String ist Altformat und wird noch akzeptiert (`normalize_preset_stack`, `:2627-2680`). `""`, `"default"`, `"_initial"` bedeuten „Standard-Preset“ (`is_preset_id_as_default`, `:2691`). Fehlt `modulePreset`, gilt das Standard-Preset des Moduls.
- Gruppen-Preset: `"groupPreset": { "module.decoration.spacing": { "presetId": ["apzj7e17ok"], "groupName": "divi/spacing" } }` (`get_selected_group_presets`, `:2021-2320`; Überlagerung `:2148`).
- Stapel: letztes Element gewinnt bei `get_selected_preset` (`:522-600`); `renderAttrs` werden nach `priority` aufsteigend gemischt (`:626-712`). Verschachtelte und explizite Gruppen-Presets werden gestapelt, nicht ersetzt (`:2148-2163`).
- Welche Gruppen ein Modul hat und wie sie heißen: `GlobalPreset::get_group_preset_default_attr(ModuleRegistration::get_module_settings($name))` (`:2332-2465`), Namensliste `get_default_group_name` (`:2475-2511`). Beispiel `divi/text`: `content.decoration.bodyFont` → `divi/font-body`, `module.decoration.spacing` → `divi/spacing`; `divi/button`: `button.decoration.font` → `divi/font`, `button.decoration.button` → `divi/button`.
**CSS-Klassen im Frontend** (`GlobalPresetItemUtils::generate_preset_class_name`, `GlobalPresetItemUtils.php:43-78`): `preset--module--divi-button--3gbmxlurey`, Standard-Preset `preset--module--divi-divider--default`, Gruppe `preset--group--divi-blurb--divi-spacing--apzj7e17ok`, verschachtelt `preset--group--divi-text--divi-font-body--nested--ja6o54jtlc`. Keine Klassen für `divi/id-classes`, `divi/animation`. Am Fixture geprüft (README in `tests/Fixtures/divi/stage4/`).
**Schreiben:** `GlobalPreset::save_data(array $data)` (`:419-431`) schreibt die **komplette Option ungeprüft**, leert Caches (`remove_static_resources('all','all',true,'all',true)` – Builder-CSS bleibt erhalten) und setzt `self::$_data` zurück. Sanitisierung gibt es nur in `GlobalPreset::prepare_data()` (`:213-268`): wandelt das REST-Format (`{module: [{default, items: [ … ]}], group: …}`, Listen statt ID-Schlüssel) ins Speicherformat, entfernt leere Attribut-Arrays und sanitisiert `attrs`/`renderAttrs`/`styleAttrs` mit `SavingUtility::sanitize_block_attrs` bzw. `sanitize_group_attrs` (`VisualBuilder/Saving/SavingUtility.php:2153`, `:2296`; ohne `unfiltered_html` wird gefiltert).
**Builder-Speicherweg:** `POST divi/v1/global-data/global-preset/sync` (`RESTRegistration.php:1035-1045`) → `GlobalPresetController::sync` (`GlobalPresetController.php:45-187`): optional in Blöcken (Transient 5 min), `prepare_data`, **`validate_preset_count`** (`GlobalPreset.php:335-410`: weniger Presets als gespeichert → Fehler 400 `preset_count_decreased`, außer `actionType` `DELETE_MODULE_PRESET`/`DELETE_OPTION_GROUP_PRESET`), bei Löschaktionen Bereinigung der Divi-4-Option (`remove_preset_from_legacy_option`), dann `save_data`. Der Builder schickt immer den **vollständigen** Bestand.
**Rechte:** `sync_permission()` = `UserRole::can_current_user_use_visual_builder()` (`GlobalPresetController.php:460`, `Framework/UserRole/UserRole.php:38-70`) – nur das Rollen-Recht „Visual Builder verwenden“ (Standard „an“), **kein** `edit_theme_options`. Dazu der Routen-Nonce wie in Abschnitt 1.
**Preset-Historie:** `et_divi_builder_presets_history_meta` (`{index, size}`) und `et_divi_builder_presets_history_item_<n>` gehören zu Divi 4 (`includes/builder/feature/global-presets/History.php:4-10`, Schreiben nur per AJAX `et_builder_save_global_presets_history` mit `switch_themes`, `:75-139`). Der Divi-5-Sync schreibt dort **nichts**; Rückgängig im Builder ist rein clientseitig. Lokal: `{index: -1, size: 0}`. Für Etappe 4 heißt das: eine Wiederherstellung gibt es nur über eigene Snapshots (Spec §7).
## 4. Theme-Optionen (`et_divi`)
Lokal ist **keiner** der Whitelist-Schlüssel gesetzt (Fixture `et-divi-subset.json`) – überall gelten Divi-Standards.
| Gruppe | Schlüssel in `et_divi` | Schreibweg in Divi | Sanitize / Standard (Customizer, `functions.php`) |
|---|---|---|---|
| Schriften | `heading_font`, `body_font`, `primary_nav_font`, `secondary_nav_font`, `all_buttons_font` | Customizer, `global-fonts`-REST (nur heading/body) | `et_sanitize_font_choices`, Standard `none` (`:2484`, `:2557`, `:3398`, `:3522`, `:4996`) |
| | `heading_font_weight`, `body_font_weight` | Customizer | `et_sanitize_global_font_weight`, `500` (`:2509`, `:2582`) |
| Größen | `body_font_size` (14), `tablet_body_font_size`, `phone_body_font_size`, `body_header_size` (30), `tablet_header_font_size`, `phone_header_font_size` | Customizer | `absint` (`:2208-2456`); Tablet/Phone-Standard = nächstgrößerer Wert |
| | `body_font_height` (1.7), `body_header_height` (1) | Customizer | `et_sanitize_float_number` (`:2236`, `:2376`) |
| | `body_header_spacing` (0), `body_header_style` (`''`) | Customizer | `et_sanitize_int_number`, `et_divi_global_font_style` (`:2348`, `:2404`) |
| Layout | `content_width` (1080), `gutter_width` (3), `section_padding` (4), `row_padding` (2) | Customizer | `absint` (`:1824`, `:1852`, `:1927`, `:2011`) |
| Farben | `accent_color`, `secondary_accent_color`, `header_color`, `font_color`, `link_color` | Customizer **und** `set_global_colors` (Abschnitt 1) | `et_sanitize_alpha_color` (`:2534-2665`) |
| Buttons | `all_buttons_*` (19 Schlüssel inkl. `*_hover`, z. B. `all_buttons_bg_color`, `all_buttons_border_radius_hover`) | Customizer | je Feld (`:4843ff`) |
| Logo | `divi_logo` | ePanel „Theme-Optionen“ | Typ `upload`, URL-String (`options_divi.php:81`) |
| SEO | `divi_seo_home_*`, `divi_seo_single_*`, `divi_seo_index_*` | ePanel | Checkboxen `'on'`/`'false'`, Texte, Selects (`options_divi.php:721-894`; Speichern `epanel/core_functions.php:868ff`, Checkbox `:969-970`/`:1049`) |
- Customizer-Einstellungen sind `type => 'option'` mit Schlüssel `et_divi[<name>]` und `capability => 'edit_theme_options'`; Divi prüft zusätzlich das Rollen-Recht `theme_customizer` (`functions.php:1278`). ePanel: `edit_theme_options` (`epanel/core_functions.php:873`) und Rollen-Recht `theme_options` (`functions.php:8834-8846`).
- **Standardwerte weichen voneinander ab:** `secondary_accent_color` hat im Customizer `#8800FF` (`functions.php:2665`), in `GlobalData::$customizer_colors` und im Builder `#2ea3f2` (`GlobalData.php:64-68`, `includes/builder/core.php:7293`). Wer die Builder-Sicht (`get_global_colors()`) zurückschreibt, setzt die Customizer-Farbe damit fest auf `#2ea3f2`. Ebenso Schriften: Customizer-Standard `none`, Builder-Sicht „Open Sans“.
- Caches: nach Customizer-Speichern `remove_static_resources('all','all')` (`core/components/PageResource.php:944`, `:1139-1141`) und `et_pb_force_regenerate_templates` (`includes/builder/core.php:3771`). Für eigene Schreibzugriffe gilt dasselbe.
## 5. Caches
`ET_Core_PageResource::remove_static_resources($post_id, $owner, $force, $slug, $preserve_vb_files, $delete_files)` (`core/components/PageResource.php:1213-1232`) läuft **nur**, wenn `wp_doing_cron()` oder `et_core_security_check_passed('edit_posts')` – ohne angemeldeten Benutzer (z. B. WP-CLI ohne `--user`) ist der Aufruf wirkungslos. Farben und Variablen rufen `('all','all',true)`, Presets `('all','all',true,'all',true)` bereits selbst auf; der in Spec §7 vorgesehene Zusatzaufruf ist harmlos, aber redundant.
In-Request-Caches: `GlobalData::$_cached_data` (Reset bei `set_*`), `GlobalPreset::$_data` und `$_normalized_preset_stack_cache` (Reset in `save_data`/`delete_data`), `static` in `get_customizer_colors()`/`get_customizer_fonts()` und `get_group_preset_default_attr()` (kein Reset).
## 6. Rechte im Überblick
| Weg | Prüfung in Divi |
|---|---|
| REST `global-colors`, `global-fonts`, `global-variables` | `edit_posts` + Routen-Nonce |
| `GlobalData::set_global_variables()` | `edit_theme_options` **und** `et_pb_is_allowed('variables_manager')`, sonst stilles `return` |
| `GlobalData::set_global_colors()`, `GlobalPreset::save_data()` | keine |
| REST `global-preset/sync` | Rollen-Recht `use_visual_builder` + Routen-Nonce |
| Customizer | `edit_theme_options` + Rollen-Recht `theme_customizer` |
| ePanel | `edit_theme_options` + Rollen-Recht `theme_options` |
| `remove_static_resources` | `edit_posts` (oder Cron), sonst wirkungslos |
## 7. Abweichungen und Ergänzungen zu Spec §7
1. **„Variablen … über `GlobalData::set_global_variables`“** – stimmt, aber die Funktion ersetzt die ganze Option, liefert keinen Fehler bei fehlenden Rechten und kennt keinen Typ `colors`. Das Tool muss vorher lesen, `stdClass` → Array wandeln, `allowedActions` und die zwei Customizer-Schriften entfernen und nach dem Schreiben per Lesen prüfen.
2. **„globale Farben … inkl. der fünf Hauptfarben (über `set_global_colors`)“** – die Hauptfarben landen in `et_divi['accent_color']` usw.; der zweite Parameter entscheidet zwischen Ersetzen (`false`) und Mischen (`true`). Für Änderungen einzelner Farben: Mischen; Löschen wie der Builder als `status: inactive`. Hauptfarben nur schicken, wenn sie wirklich geändert werden sollen (Standardwert-Abweichung, Abschnitt 4).
3. **„Presets … per Name“** – Namen sind nicht eindeutig; IDs sind 10-stellige Zeichenketten. Schreiben immer als vollständiger Bestand; `save_data()` sanitisiert nicht → vorher `prepare_data()` (REST-Format) und `validate_preset_count()` wie der Controller verwenden. `styleAttrs`/`renderAttrs` müssen mitgeschrieben werden (Frontend liest sie).
4. **„Voraussetzung: `edit_theme_options`; für Variablen zusätzlich `variables_manager`“** – strenger als Divi selbst (Farben: `edit_posts`; Presets: nur Visual-Builder-Recht). Als bewusste Plugin-Entscheidung beibehalten; für Variablen ist die Doppelprüfung zwingend, weil Divi sonst still nichts tut.
5. **„danach `remove_static_resources('all','all',true)`“** – Divis Setter tun das schon; wirkungslos ohne angemeldeten Benutzer. Für Presets nutzt Divi `preserve_vb_files = true`.
6. **Snapshots** sind die einzige Wiederherstellung: Divi 5 führt für Farben, Variablen und Presets keine serverseitige Historie; die Option `et_divi_builder_presets_history_meta` gehört zu Divi 4.
7. **Theme-Optionen:** `divi_logo` und `divi_seo_*` sind ePanel-Felder (andere Sanitize-Regeln, Checkbox-Werte `'on'`/`'false'`), keine Customizer-Einstellungen. Die übrigen Whitelist-Felder haben die Customizer-Sanitize-Callbacks aus Abschnitt 4 – diese sollten wiederverwendet werden.
8. **Lesepfad des Plugins** (`src/Integrations/Divi/Design/DesignSystem.php`): Farb-„Variablen“ (`variables['colors']`) sind in 5.2.1 immer leer – globale Farben tragen Label und ID selbst. `fonts()` liefert die Builder-Sicht („Open Sans“), nicht den Customizer-Standard `none`.
## 8. Stolpersteine (Kurzliste)
- `get_global_variables()` liefert `stdClass` je Gruppe, gespeichert sind Arrays; leere Gruppe = `{}` im JSON.
- Variableneinträge brauchen das Feld `id`; `order` wird zum String.
- `usedInPosts` muss ein Array sein; wird nur vom Builder gepflegt.
- `created`/`updated` in Millisekunden (Builder) vs. Sekunden (PHP-Hilfen).
- `modulePreset` ist ein Array; Standard-Preset wirkt nur mit `attrs`.
- Sync-Endpunkt verweigert kleinere Preset-Anzahl ohne `DELETE_*`-actionType.
- `GlobalPreset::delete_data()` löscht die Product-Setting-Option nicht.
- Divi-REST-Routen verlangen `X-ET-Nonce` pro Route; direkte PHP-Aufrufe umgehen Nonce, Schema und Rechteprüfung.
- `secondary_accent_color`: Customizer-Standard `#8800FF`, Builder-Standard `#2ea3f2`.