# Etappe 4d – Divi 5.15.1: Theme-Optionen und Schriften (Code-Recherche) Stand: 2026-10-19. Grundlage der Rulings W1–W14 (siehe `2026-09-28-etappe-4-entscheidungen-entwurf.md`, Abschnitt „Etappe 4d“). Die Befunde in Abschnitt 0 sind in den Formate-Nachtrag „Etappe 4d“ übernommen. Quelle: installiertes Divi 5.15.1 (`style.css:4`), WordPress 7.1.3. Alle Pfade relativ zu `wp-content/themes/Divi`, sofern nicht anders angegeben. Nur aus Code abgeleitet; die Zeilen der Formate-Notiz §4 (5.2.1) sind durchweg um ca. +80 verschoben. ## 0. Überraschungen gegenüber Spec §8 (Kurzfassung) 1. **`FontsUtility::font_add()` ist in-process aufrufbar.** `wp_handle_upload()` nimmt `$overrides['action']` als Action (`wp-admin/includes/file.php:1110`); nur bei Action `'wp_handle_upload'` prüft `_wp_handle_upload` `is_uploaded_file()`/`move_uploaded_file()` (`file.php:936`, `:1019-1027`), sonst `is_readable()` + `copy()`/`unlink()`. Divi übergibt selbst `['action' => 'wp_handle_upload_rest']` (`CustomFontController.php:68-75`) und installiert so **serverseitige Temp-Dateien**: `AiAgentImageUtility::install_to_custom_fonts()` (`includes/builder-5/server/VisualBuilder/AiAgent/AiAgentImageUtility.php:860-1009`, `wp_tempnam` → `copy` → `font_add([...'tmp_name'=>$temp...], …, ['action'=>'wp_handle_upload_rest'])`). → Kein eigener Sideload und kein Nachbau des `et_uploaded_fonts`-Eintrags nötig; Spec §8.2 („nur echte Browser-Uploads“, „Nachbau mit Fingerabdruck“) ist überholt. Mirror höchstens für die *Aufrufkonvention* (`install_to_custom_fonts`). 2. **Divi-Rolle für Schriften ist `custom_fonts_management`**, nicht `use_visual_builder` (`CustomFontController.php:141-152`, `:214-225`; Rollen-Editor `includes/builder/functions.php:9595`). Plus `upload_files`. Spec §10 nachziehen. 3. **Standard-Formate nur `ttf`, `otf`:** `et_pb_get_supported_font_formats()` (`includes/builder/core.php:5090`, Filter `et_pb_supported_font_formats`). Der REST-Store nimmt nur diese Felder an; `font_add` selbst akzeptiert alles aus `mime_types_font()` (otf, ttf, woff, woff2, eot), das Frontend (`et_builder_enqueue_user_fonts`, `includes/builder/functions.php:3813ff`) gibt alle fünf aus. woff/woff2 sind also technisch möglich, aber über Divis UI nicht vorgesehen → Entscheidung nötig. 4. **„Standard-Schrift“ wird im Customizer als `false` gespeichert, nicht als `'none'`.** `et_sanitize_font_choices('none')` → `false` (`'none'` steht nicht in `et_builder_get_fonts()`), WP speichert `false` (nur `null`/`WP_Error` werden verworfen, `wp-includes/class-wp-customize-manager.php:1848-1851`). Leser: Divi-4-CSS/Google-URL `et_pb_get_specific_default_font()` behandelt `false`/`''`/`'none'` gleich (`functions.php:1287-1290`, lose `in_array`); Divi 5 `Fonts::enqueue()` und `GlobalData::get_customizer_fonts()` nehmen bei *fehlendem* Schlüssel `'Open Sans'`, bei `false` ebenfalls `'Open Sans'` (`FrontEnd/Module/Fonts.php:100-129`), bei `'none'` aber `self::add('none')` (`:138-144`). → „Standard“ = Schlüssel entfernen (`et_delete_option`), nie `'none'` schreiben. 5. **Divis Sanitizer prüfen keine Bereiche.** `absint`, `intval`, `floatval` klemmen nicht auf min/max der Controls; `et_sanitize_*_choices` liefern bei Unbekanntem `false` (kein Fehler). Plugin muss Bereiche aus den Control-`input_attrs` selbst prüfen und `false` als Ablehnung werten. 6. **`body_header_style` ist im Control mehrfach wählbar (`italic|uppercase`, Checkbox-Liste mit `|`, `functions.php:5457-5481`), der Sanitizer `et_divi_global_font_style` akzeptiert aber nur genau einen Schlüssel** → jede Kombination und auch `''` (alles abgewählt) wird zu `false`. Divi-Bug; Tool sollte nur einen Wert (oder „unset“) zulassen. 7. **`et_builder_get_google_fonts()` kann einen HTTP-Request auslösen** (`et_builder_google_fonts_sync()`, `includes/builder/core.php:4868-4905`: einmal je 24 h per Transient `fonts_cache_status`, nur wenn Google-API-Key gesetzt). Sonst Option `et_google_fonts_cache` bzw. 1,3-MB-JSON (`core/json-data/google-fonts.json`, 1945 Familien), statisch gecacht. `et_sanitize_font_choices` ruft das bei jedem Aufruf (ksort über ~1950 Einträge) → Liste je Request einmal holen. 8. **Namens-Eindeutigkeit in `font_add` prüft nur Google-Fonts und `et_uploaded_fonts`, nicht Web-Safe-Fonts** (`FontsUtility.php:125-131`) – „Arial“ wäre hochladbar. Plugin-Regel strenger halten. 9. **`font_add` meldet Teilerfolg als Erfolg:** schlägt eine von mehreren Dateien fehl, wird die Schrift mit den übrigen gespeichert, die Fehler gehen verloren (`FontsUtility.php:157-190`). Plugin: vorab jede Datei mit `HooksRegistration::check_filetype_and_ext_font()` prüfen (wie Divis AI-Pfad) und Ergebnis `font_file`-Schlüssel gegen Eingabe vergleichen. 10. **`ET_Core_PageResource::remove_static_resources` hat jetzt 7 Parameter** (`+ $skip_external_cache`, `core/components/PageResource.php:1398`); Signatur-Fingerabdruck prüfen. 11. **`GlobalData::get_customizer_fonts()` cached per `static` ohne Reset** (`GlobalData.php:446-451`) → Zurücklesen nach dem Schreiben im selben Request über `get_option('et_divi')`/`et_get_option`, nicht darüber. 12. **`et_update_option` schreibt das ganze `et_divi`-Array aus dem Request-Global `$et_theme_options`** (`epanel/custom_functions.php:297-310`); wurde das Global früher im Request geladen und die Option inzwischen von einem anderen Prozess geändert, werden dessen Änderungen überschrieben (Lost Update). Für `design_rev`-Vergleich und Snapshot `get_option('et_divi')` lesen; Restrisiko dokumentieren. ## A. Theme-Optionen Allgemein für alle 20 Settings (Registrierung in `et_divi_customizer_theme_settings()`, `functions.php:1371ff`, Hook `customize_register` → `et_divi_customize_register` `functions.php:1306`/`:1363`): - `type => 'option'`, ID `et_divi[]` → Speicher `get_option('et_divi')[]` (Divi: `$shortname = 'divi'`, `$et_store_options_in_one_row = true`, `functions.php:16-17`). - `capability => 'edit_theme_options'`, `transport => 'postMessage'`. - Sanitizer sind **globale Funktionen**, immer geladen: `includes/functions/sanitization.php` und `choices.php` werden in `et_setup_theme()` (`after_setup_theme`, `functions.php:50-52`, `:150`) ohne Bedingung eingebunden; Builder-Listen in `includes/builder/core.php` (immer geladen). WP ruft sie als Filter `customize_sanitize_{id}` mit `($value, $setting)` (`class-wp-customize-setting.php:581`); alle Divi-Sanitizer nehmen nur `$value`, also direkt mit 1 Argument aufrufbar. Kein Customizer-Kontext nötig. - Rückgabe `false` bei ungültiger Auswahl (`et_sanitize_key_based_option($choosen, $options, $default = false)`, `sanitization.php:45-52`; `isset($options[$choosen])`, also **case-sensitiv**, numerische String-Schlüssel ≙ int). - Gespeicherte Typen: `absint` → int, `et_sanitize_float_number` → float, `et_sanitize_int_number` → int, Auswahl-Sanitizer → der übergebene Wert (String) oder `false`. | Schlüssel | Setting (file:line) | Standard (Setting) | sanitize_callback (Definition) | Control / Choices / min–max–step | Zusammenspiel / Leser | |---|---|---|---|---|---| | `heading_font` | `functions.php:2563-2572`, Control `:2574-2586` | `'none'` | `et_sanitize_font_choices($choosen)` `sanitization.php:59-66` (Google+Websafe via `et_builder_get_fonts()` + `et_builder_get_custom_fonts()`) | `ET_Divi_Select_Option`, `type select`, Choices `$font_choices` (`functions.php:1407-1440`: `'none'` + alle Fonts, im UI nach Locale-Zeichensatz gefiltert, Sanitizer filtert **nicht** nach Locale) | `'none'` → `false` gespeichert (§0.4). Builder/D5-Sicht `'Open Sans'` (`GlobalData::$customizer_fonts` `GlobalData.php:108-119`, `Fonts.php:100`). Speist `--et_global_heading_font` (`Fonts.php:148-175`), D4-Font-URL `functions.php:432`, `:7216`, `et_builder_get_font_family('--et_global_heading_font')` `includes/builder/core.php:4683-4686`. Alias `Source Sans Pro`→`Source Sans 3` nur im D5-Enqueue (`Fonts.php:113-127`). | | `body_font` | `functions.php:2636-2645`, Control `:2647-2659` | `'none'` | `et_sanitize_font_choices` | wie oben | wie oben, `--et_global_body_font`; zusätzlich `functions.php:334`, `:474`, `includes/builder/functions.php:4254`, `Fonts.php:134-136` (`'none'`/`''` = „Theme lädt Open Sans selbst“). | | `primary_nav_font` | `functions.php:3477-3486`, Control `:3488-3500` | `'none'` | `et_sanitize_font_choices` | Select, `$font_choices` (Section `et_divi_header_primary`) | nur D4-Header/Google-URL (`functions.php:435`); kein D5-Global. | | `secondary_nav_font` | `functions.php:3601-3610`, Control `:3612-3624` | `'none'` | `et_sanitize_font_choices` | Select, `$font_choices` (Section `et_divi_header_secondary`) | `functions.php:436`; kein D5-Global. | | `heading_font_weight` | `functions.php:2588-2597`, Control `:2599-2611` | `'500'` | `et_sanitize_global_font_weight($choosen)` `sanitization.php:345-347` | `ET_Divi_Custom_Select_Option`, Choices `et_builder_get_font_weight_list()` (`includes/builder/core.php:4792-4807`: `'100'…'900'`, Filter `et_builder_all_font_weights`) | `--et_global_heading_font_weight` (`Fonts.php:151`, leer → `'500'`), D4-CSS `functions.php:5827`, `:6943ff` (nur wenn ≠ 500). | | `body_font_weight` | `functions.php:2661-2670`, Control `:2672-2684` | `'500'` | `et_sanitize_global_font_weight` | wie oben | `--et_global_body_font_weight`, `functions.php:5828`, `:5992ff`. | | `body_font_size` | `functions.php:2287-2296`, Control `:2298-2313` | `'14'` | `absint` (WP) | `ET_Divi_Range_Option`, `range` 10–32, step 1 | `--et_global_body_font_size` (`Fonts.php:104-107`, `''` → 14), D4 `functions.php:5819`, VB-Settings `SettingsDataCallbacks.php:2379`. | | `tablet_body_font_size` | `functions.php:2371-2380`, Control `:2382-2397` | `et_get_option('body_font_size','14')` (dynamisch) | `absint` | Range 10–32, step 1 (Section `et_divi_mobile_tablet`) | Leser nehmen fest `'14'` (`functions.php:5841`, `Fonts.php:108`), CSS nur wenn `≠14 && ≠ body` (`functions.php:7019`) → unset ≙ erbt Desktop. | | `phone_body_font_size` | `functions.php:2343-2352`, Control `:2354-2369` | `et_get_option('tablet_body_font_size','14')` | `absint` | Range 10–32, step 1 (`et_divi_mobile_phone`) | Leser: Standard = Tablet-Wert (`functions.php:5846`, `Fonts.php:109`); CSS nur wenn `≠14 && ≠ tablet` (`:7112`). | | `body_header_size` | `functions.php:2399-2408`, Control `:2410-2425` | `'30'` | `absint` | Range 22–72, step 1 | `functions.php:5821`, VB `SettingsDataCallbacks.php:2391`. | | `tablet_header_font_size` | `functions.php:2535-2544`, Control `:2546-2561` | `et_get_option('body_header_size','30')` | `absint` | Range 22–72, step 1 | Leser fest `'30'` (`functions.php:5840`), CSS nur wenn `≠30 && ≠ body_header_size` (`:7039`). | | `phone_header_font_size` | `functions.php:2507-2516`, Control `:2518-2533` | `et_get_option('tablet_header_font_size','30')` | `absint` | Range 22–72, step 1 | Standard = Tablet (`functions.php:5845`), CSS `:7132`. | | `body_font_height` | `functions.php:2315-2324`, Control `:2326-2341` | `'1.7'` | `et_sanitize_float_number($number)` `sanitization.php:7-9` (`floatval`) | Range 0.8–3, step 0.1 | `--et_global_body_font_height` (`Fonts.php:105-107`, Einheit `em`), D4 `functions.php:5820`, VB `:2378`. | | `body_header_height` | `functions.php:2455-2464`, Control `:2466-2481` | `'1'` | `et_sanitize_float_number` | Range 0.8–3, step 0.1 | D4 `functions.php:5824`, `:6604`. | | `body_header_spacing` | `functions.php:2427-2436`, Control `:2438-2453` | `'0'` | `et_sanitize_int_number($number)` `sanitization.php:16-18` (`intval`) | Range −2–10, step 1 | D4 `functions.php:5823`, `:6604`. Negativ erlaubt. | | `body_header_style` | `functions.php:2483-2492`, Control `:2494-2505` | `''` | `et_divi_global_font_style($choosen)` `sanitization.php:358-360` | `ET_Divi_Font_Style_Option` (`functions.php:5457`, Checkboxen, Wert `a|b`), Choices `et_divi_global_font_style_choices()` (`includes/functions/choices.php:23-33`: `italic`, `uppercase`, `underline`; Filter `et_divi_global_font_style_choices`) – **kein `bold`** (das gibt es nur bei `et_divi_font_style_choices`/`et_sanitize_font_style`) | Sanitizer nimmt nur genau einen Wert, `''` und Kombinationen → `false` (§0.6). Leser `et_get_option('body_header_style','','',true)` + `et_pb_print_font_style()` (`functions.php:5822`, `:6616`). | | `content_width` | `functions.php:1903-1912`, Control `:1914-1929` | `'1080'` | `absint` | Range 960–1920, step 1 (`et_divi_general_layout`) | D4 `functions.php:5833`, `:6133`; VB `SettingsDataCallbacks.php:2387`. | | `gutter_width` | `functions.php:1931-1940`, Control `:1942-1957` | `'3'` | `absint` | Range 1–4, step 1 | Standard für Section/Row-`gutter_width` (D4 `includes/builder/main-structure-elements.php:239`, `:1953`), Body-Klasse `functions.php:8581`. In D5-Servercode kein Leser gefunden. | | `section_padding` | `functions.php:2006-2015`, Control `:2017-2032` | `'4'` | `absint` | Range 0–10, step 1 | D4 `functions.php:5837`. | | `row_padding` | `functions.php:2090-2099`, Control `:2101-2116` | `'2'` | `absint` | Range 0–10, step 1 | D4 `functions.php:5838`. | Abhängige Standards: `tablet_*` / `phone_*` werden im Customizer beim Registrieren aus dem aktuellen Größeren berechnet; die CSS-Leser nutzen feste 14/30 und „gleich dem Größeren = keine Regel“. Für das Tool: Standard von `tablet_body_font_size` = effektiver `body_font_size`, `phone_*` = effektiver `tablet_*`; „unset“ heißt „erbt“. ### A.1 Builder-Globals (`--et_global_*`) - `GlobalData::$customizer_fonts` (`includes/builder-5/server/Packages/GlobalData/GlobalData.php:108-119`): `--et_global_heading_font` → `heading_font`, `--et_global_body_font` → `body_font`, beide Standard `'Open Sans'`. `get_customizer_fonts()` (`:446-470`) liefert `{id,label,value,status:'active',order}`, `static` gecacht. - Frontend-Variablen (`FrontEnd/Module/Fonts.php:99-191`, `Fonts::enqueue()`): `--et_global_heading_font`, `--et_global_body_font`, `--et_global_heading_font_weight`, `--et_global_body_font_weight`, `--et_global_body_font_size` (+ responsive aus `tablet_/phone_body_font_size`), `--et_global_body_font_height` – also speisen **heading_font, body_font, heading_font_weight, body_font_weight, body_font_size, tablet_body_font_size, phone_body_font_size, body_font_height** Divi-5-Globals; inline ausgegeben (`