Files
WP-LIT-MCP/docs/superpowers/notes/2026-10-19-etappe-4d-divi-recherche.md

32 KiB
Raw Permalink Blame History

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[<key>] → Speicher get_option('et_divi')[<key>] (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`)
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 (<style class="et-vb-global-data et-vb-global-fonts">), nicht im Static-CSS gecacht.
  • REST POST /global-data/global-fonts (includes/builder-5/server/VisualBuilder/REST/RESTRegistration.php:1677) → GlobalDataController::update_global_fonts() (GlobalDataController.php:166-191): et_update_option('heading_font', …), et_update_option('body_font', …) (Args required, nur sanitize_text_field, :215-227, keine Font-Prüfung), dann ET_Core_PageResource::remove_static_resources('all','all',true). Rechte nur edit_posts (:243-249) + Routen-Nonce. Weitere Schreiber derselben Schlüssel: AJAX et_update_customizer_fonts (includes/builder/functions.php:13583-13603, edit_theme_options), Import PortabilityPost.php:4130-4136.

A.2 Was ein Customizer-Speichern zusätzlich tut

  • WP schreibt das Setting direkt per update_option('et_divi', …) (multidimensional), nicht über et_update_option.
  • customize_save_after-Hooks in Divi (vollständig per grep): ET_Core_PageResource::customize_save_after_cb → remove_static_resources('all','all') (core/components/PageResource.php:953, :1252-1254) und et_pb_force_regenerate_templates (includes/builder/core.php:3811-3817, :3825: et_update_option('et_pb_clear_templates_cache', true) + et_fb_delete_builder_assets() core.php:6592ff, löscht VB-Helper-JS/.data/Bild-Caches).
  • Keine Hooks auf update_option_et_divi / pre_update_option_et_divi in Divi.
  • remove_static_resources (PageResource.php:1398-1417): wirkungslos bei DOING_AUTOSAVE, ohne wp_doing_cron() bzw. et_core_security_check_passed('edit_posts'), oder ohne Schreibrecht im Dateisystem; $force löscht nur zusätzlich die Option et_core_page_resource_remove_all (:1596-1598); $skip_external_cache=false ruft et_core_clear_wp_cache() (Fremd-Page-Caches). Die Customizer-CSS liegt im Static-Resource et-divi-customizer-global-* (functions.php:5790, :7331) und wird damit invalidiert.
  • et_update_option (epanel/custom_functions.php:276-315): lädt $et_theme_options nur, wenn das Global fehlt oder is_customize_preview(); setzt Schlüssel, update_option('et_divi', $et_theme_options); aktualisiert damit auch das Global (spätere et_get_option im Request sehen den neuen Wert). Keine weiteren Seiteneffekte.

A.3 Minimale korrekte In-Process-Schreibfolge (≙ Customizer-Speichern)

  1. Rechte: current_user_can('edit_theme_options') und et_pb_is_allowed('theme_customizer') (Customizer: functions.php:1357, wp_die; Rollen-Editor includes/builder/functions.php:9523). et_pb_is_allowed (includes/builder/core.php:1136-1170) ist ohne Eintrag im Rollen-Editor true (nicht „dangerous“); nur eine Fähigkeit je Aufruf übergeben (bei Arrays entscheidet die erste gespeicherte).
  2. Je Schlüssel: Divi-Sanitizer aufrufen; false → Validierungsfehler (statt wie Divi false zu speichern); Bereich aus Tabelle selbst prüfen; Ergebnis vergleichen (z. B. absint('15.7') = 15 → als Änderung melden oder ablehnen).
  3. Schreiben: et_update_option($key, $sanitized) je Schlüssel; „unset“: et_delete_option($key).
  4. Caches: ET_Core_PageResource::remove_static_resources('all', 'all') (Customizer) – oder ('all','all',true) wie global-fonts; plus et_pb_force_regenerate_templates(). Benutzer muss angemeldet sein (edit_posts).
  5. Zurücklesen: get_option('et_divi') (WP-Cache wird von update_option aktualisiert), nicht GlobalData::get_customizer_fonts().

B. Schriften

B.1 Listen

Funktion Ort Signatur Rückgabe / Kosten
et_builder_get_google_fonts() includes/builder/core.php:4911-4929 () [] wenn Google Fonts aus (et_core_use_google_fonts(), core/functions.php:223-228, Option et_google_api_settings['use_google_fonts'], Standard 'on'). Sonst ruft et_builder_google_fonts_sync() (:4868-4905, ggf. wp_remote_get an Google, 1×/24 h, nur mit API-Key) und liefert Option et_google_fonts_cache (Array oder JSON-String, dann jedes Mal geparst) bzw. et_core_get_saved_google_fonts() (core/functions.php:1970-1985, 1,3 MB JSON, 1945 Familien, static). Filter et_builder_google_fonts. Form: `Family => {styles:'100,100italic,…', character_set:'latin,…', type:'sans-serif
et_builder_get_websafe_fonts() core.php:4783-4785 () → et_core_get_websafe_fonts() (core/functions.php:1993-2030): Arial, Georgia, Times New Roman, Trebuchet, Verdana mit styles, character_set, type, ggf. add_ms_version; Filter et_websafe_fonts.
et_builder_get_fonts( $settings = array() ) core.php:4730-4751 0–1 Arg (prepend_standard_fonts) Websafe + Google, ksort; bei abgeschalteten Google Fonts nur Websafe. Ohne hochgeladene Schriften.
et_builder_get_custom_fonts() core.php:4822-4831 () get_option('et_uploaded_fonts', []), nicht-Array → [], Filter et_builder_custom_fonts.
et_builder_get_font_family( $font_name, $use_important = false ) core.php:4683-4723 1–2 CSS-String font-family: '…', <stack>; – kein Listen-Lieferant. Löst --et_global_*_font auf.
et_builder_old_fonts_mapping() core.php:4839ff () Alt-Namen (Raleway Light, Open Sans Light …) → parent_font + Gewicht.
et_builder_get_font_weight_list() core.php:4792-4807 () '100'…'900' → Label.

Gesamtliste wie der Customizer-Sanitizer: array_merge(et_builder_get_custom_fonts(), et_builder_get_fonts()) (sanitization.php:59-66). Gültiger Wert = exakter Familienname als Schlüssel (case-sensitiv), keine :/|-Qualifizierer; 'none' ist kein Schlüssel. Gewichte/Stile sind eigene Settings.

B.2 Hochgeladene Schriften

Option et_uploaded_fonts (autoload Standard), geschrieben von FontsUtility::font_add():

'<Name>' => [
  'font_file' => [ '<ext>' => esc_url('<absoluter Dateipfad>') , … ],   // Pfad durch esc_url
  'font_url'  => [ '<ext>' => esc_url('<URL>'), … ],
  'styles'    => '100,200,…,900' | '<eingegebene Liste>',               // nur wenn $font_settings nicht leer
  'type'      => '<generic_family>' | 'serif',                          // nur wenn $font_settings nicht leer
]

(Divi-4-Altformat von et_pb_add_font, includes/builder/core.php:5183-5270, ist gleich; font_file/font_url können dort auch Strings sein – et_builder_enqueue_user_fonts behandelt beides, functions.php:3818, :3869.)

FontsUtility::font_add( array $font_files, string $font_name, array $font_settings, array $overrides = [] ) (includes/builder-5/server/VisualBuilder/Fonts/FontsUtility.php:102-213, Namespace ET\Builder\VisualBuilder\Fonts):

  • $font_files: ['<ext>' => <$_FILES-Element {name,type,tmp_name,error,size}>] (nicht Pfade, trotz Docblock). Leer → WP_Error file_empty.
  • Name: preg_replace('/[^A-Za-z0-9\s\_-]/', '', $name) (stilles Entfernen, kein Fehler) → leer: font_name_empty; > FONT_NAME_MAX_LENGTH (100, :39) Zeichen nach StringUtility::strlen_utf8: font_name_too_long; bereits in get_option('et_uploaded_fonts') (ungefiltert) oder et_builder_get_google_fonts() (exakt, case-sensitiv): font_name_exists. Websafe nicht geprüft. Der tatsächlich gespeicherte Name steht in uploaded_font der Rückgabe.
  • Ablage: add_filter('upload_dir', [HooksRegistration::class, 'upload_dir_font']) → <uploads basedir>/et-fonts, URL <baseurl>/et-fonts, subdir '/et-fonts' (keine Jahr/Monat-Ordner; HooksRegistration.php:318-332); add_filter('wp_check_filetype_and_ext', [HooksRegistration::class, 'check_filetype_and_ext_font'], 999, 3); beide nach der Schleife entfernt (:174-178).
  • Je Datei wp_handle_upload($file, wp_parse_args(['test_size'=>true,'test_type'=>true,'test_form'=>false], $overrides)) → die drei Tests sind fest, action (und andere Overrides wie unique_filename_callback, mimes) kommen durch. Dateiname wp_unique_filename → keine Überschreibung.
  • Typprüfung check_filetype_and_ext_font (HooksRegistration.php:77-127): mit fileinfo: Endung (lowercase) muss in mime_types_font() stehen und finfo (Magic) einen der MIME-Typen liefern: otf font/otf|application/x-font-opentype|application/x-font-ttf|application/vnd.ms-opentype, ttf font/ttf|font/sfnt|application/font-sfnt|application/x-font-ttf, woff font/woff|application/font-woff, woff2 font/woff2|application/font-woff2, eot application/vnd.ms-fontobject (FontsUtility.php:290-316); proper_filename = sanitize_file_name(). Ohne fileinfo: Fallback auf WPs Ergebnis. Achtung: _wp_handle_upload lässt bei current_user_can('unfiltered_upload') auch fehlgeschlagene Typprüfung durch (wp-admin/includes/file.php:971) → eigene Magic-Byte-Prüfung vorab ist Pflicht (Spec hat sie schon).
  • Gewichte: font_weights fehlt oder 'all' → '100,200,300,400,500,600,700,800,900', sonst ungeprüft übernommen; generic_family ungeprüft, Standard 'serif'. Kein Größenlimit außer test_size (>0).
  • Ergebnis: mind. eine Datei ok → update_option('et_uploaded_fonts', …), do_action('divi_visual_builder_fonts_custom_font_added') (→ et_fb_delete_builder_assets, HooksRegistration.php:345), Rückgabe ['uploaded_font' => $name, 'updated_fonts' => $all]. Keine Datei ok → WP_Error (gesammelte Upload-Fehler, ggf. fileinfo_extension_not_loaded, sonst upload_error_unknown).
  • Keine Rechte-/Nonce-Prüfung in font_add/font_remove; die liegt im REST-Controller: CustomFontController::store_permission() / destroy_permission() (includes/builder-5/server/VisualBuilder/REST/CustomFont/CustomFontController.php:141-152, :214-225): upload_files + et_pb_is_allowed('custom_fonts_management'); Routen POST /custom-font/add, /custom-font/remove (RESTRegistration.php:1168-1183) mit Divi-Routen-Nonce. Store nimmt nur et_pb_font_file_<fmt> für et_pb_get_supported_font_formats() (Standard ttf, otf).

In-process mit Nicht-HTTP-Dateien: möglich. wp_handle_upload() setzt $action = $overrides['action'] ?? 'wp_handle_upload' (wp-admin/includes/file.php:1110); _wp_handle_upload prüft is_uploaded_file() und nutzt move_uploaded_file() nur bei 'wp_handle_upload' === $action, sonst is_readable(), filesize(), copy() + unlink(tmp); zusätzlich laufen {$action}_prefilter/{$action}_overrides-Filter (also wp_handle_upload_rest_prefilter, nicht wp_handle_upload_prefilter). Divi macht genau das in AiAgentImageUtility::install_to_custom_fonts() (AiAgentImageUtility.php:860-1009): check_filetype_and_ext_font() vorab, wp_tempnam($filename), copy, font_add([$ext => ['name'=>$filename,'type'=>$mime,'tmp_name'=>$temp,'error'=>0,'size'=>filesize($temp)]], $name, ['font_weights'=>'all','generic_family'=>'sans-serif'], ['action'=>'wp_handle_upload_rest']), danach Temp löschen. → Plugin: wp_safe_remote_get → Temp (wp_tempnam, Name mit korrekter Endung) → Magic-Bytes + check_filetype_and_ext_font → font_add(..., ['action' => 'wp_handle_upload_rest']). Kein Sideload-Nachbau, kein Nachbau des Options-Formats. wp_handle_sideload wäre gleichwertig, aber unnötig.

FontsUtility::font_remove( string $font_name ) (:242-276): '' → file_name_empty; fehlt Schlüssel oder font_file → font_not_exist; jede Datei et_pb_safe_unlink_font_file() (includes/builder/core.php:5336-5381: nur echte Pfade unter uploads/et-fonts, Font-Endung), unset, update_option, do_action('divi_visual_builder_fonts_custom_font_removed'). Für Rollback nach fehlgeschlagenem Zurücklesen nutzbar.

Zu rufende Divi-Symbole für den Upload: FontsUtility::font_add (4), HooksRegistration::check_filetype_and_ext_font (3, Vorprüfung je Datei), FontsUtility::mime_types_font (0, erlaubte Endungen), FontsUtility::FONT_NAME_MAX_LENGTH (Konstante), et_pb_get_supported_font_formats (0, falls Divi-UI-Formate gelten sollen), et_builder_get_google_fonts (0) / et_builder_get_websafe_fonts (0) / et_builder_get_custom_fonts (0) für Namensprüfung, et_pb_is_allowed (1, custom_fonts_management), optional FontsUtility::font_remove (1) für Rollback. HooksRegistration::upload_dir_font muss nicht gerufen werden (setzt font_add selbst).

Mirror-Kandidaten (statt Speicher-Nachbau):

  • new Mirror('font-install', 'includes/builder-5/server/VisualBuilder/AiAgent/AiAgentImageUtility.php', 'install_to_custom_fonts', 'AiAgentImageUtility', 'Aufrufkonvention von font_add mit Temp-Datei (check_filetype_and_ext_font, wp_tempnam, action wp_handle_upload_rest)') – fängt, wenn Divi den In-Process-Weg ändert.
  • Optional new Mirror('font-add', 'includes/builder-5/server/VisualBuilder/Fonts/FontsUtility.php', 'font_add', 'FontsUtility', 'Namensregeln und Eintragsformat von et_uploaded_fonts (Plugin-Vorprüfung, Rückgabe-Vergleich)') – nur nötig, wenn das Plugin Namensregeln (Regex, 100 Zeichen, Kollision) vorab nachbildet, um im Probelauf ohne Upload zu melden.
  • new Mirror('customizer-font-sanitize', 'includes/functions/sanitization.php', 'et_sanitize_font_choices', null, …) nur, wenn die Font-Validierung nicht über den Sanitizer, sondern über die gemergte Liste nachgebaut wird.

C. Snapshot / Restore „theme_options“

  • Inhalt: genau die 20 Whitelist-Schlüssel aus get_option('et_divi'), je Schlüssel vorhanden + Rohwert (mit Typ: int/float/string/false) oder nicht vorhanden. Darstellung z. B. {"set": {key: value, …}, "unset": [key, …]} oder Wert-Objekt {present: bool, value} – wichtig ist die Unterscheidung „fehlt“ vs. false vs. '', weil et_get_option bei vorhandenem Schlüssel (auch false) den Wert, sonst den Standard liefert (custom_functions.php:238, :246-248; Bedingung ! empty($default_value)). Farben (accent_color …) nicht hier (Snapshot colors).
  • Restore: für „vorhanden“ et_update_option($key, $raw) (Rohwert ohne Sanitizer zurückschreiben – er stammt aus Divi), für „fehlt“ et_delete_option($key); danach Caches wie A.3. et_update_option allein reicht nicht, weil es Schlüssel nicht entfernen kann.
  • et_delete_option( $option_name ) (epanel/custom_functions.php:320-333, 1 Arg, untypisiert): lädt das Global nur, wenn nicht gesetzt (ignoriert is_customize_preview), unset, update_option('et_divi', …). Entfernt den Schlüssel also wirklich. Sonderfall: ist et_divi gar nicht vorhanden (false), schreibt es false zurück (unkritisch).
  • „Unset“ im Tool-Input: eigener Wert (z. B. null = „auf Divi-Standard zurück“) → et_delete_option. Nicht 'none', nicht false schreiben.
  • get liefert Divi-Standard je Feld aus der Tabelle (Settings-default; für tablet_*/phone_* den effektiven Wert des Größeren; für Schriften Customizer-Standard none = „Theme-Standard“, Builder-Anzeige Open Sans).

D. Divi-Symbole für das Manifest

Arg-Anzahl = was das Plugin übergibt; „untyp.“ = Parameter ohne Typ-Deklaration (Signatur-Fingerabdruck weniger aussagekräftig).

Optionen lesen

  • et_get_option – 2 (von 8), untyp. (epanel/custom_functions.php:215) – optional; für Rohsicht direkt get_option('et_divi') (Key et_divi).
  • ET\Builder\Packages\GlobalData\GlobalData::get_customizer_fonts – 0, typisiert (): array (GlobalData.php:446) – nur Builder-Sicht, gecacht.
  • Keys: et_divi, et_uploaded_fonts, et_google_fonts_cache, et_google_api_settings, Statische Eigenschaft GlobalData::$customizer_fonts.

Standards / Choices

  • et_builder_get_font_weight_list – 0 (includes/builder/core.php:4792).
  • et_divi_global_font_style_choices – 0 (includes/functions/choices.php:23).
  • Standardwerte und min/max/step selbst sind nur in et_divi_customizer_theme_settings() (functions.php:1371) als Literale → nicht aufrufbar (braucht WP_Customize_Manager) → Mirror new Mirror('customizer-typography', 'functions.php', 'et_divi_customizer_theme_settings', null, 'Standards, Sanitize-Callbacks und Bereiche der 20 Whitelist-Settings') (große Funktion; Fingerabdruck schlägt bei jeder Customizer-Änderung an – akzeptabel oder engere Referenz wählen).

Sanitize

  • absint (WP-Core, nicht ins Manifest).
  • et_sanitize_float_number – 1, untyp. (includes/functions/sanitization.php:7).
  • et_sanitize_int_number – 1, untyp. (:16).
  • et_sanitize_font_choices – 1, untyp. (:59).
  • et_sanitize_global_font_weight – 1, untyp. (:345).
  • et_divi_global_font_style – 1, untyp. (:358).
  • (indirekt et_sanitize_key_based_option – 3, untyp., :45.)

Schreiben

  • et_update_option – 2 (von 6), untyp. (epanel/custom_functions.php:276).
  • et_delete_option – 1, untyp. (:320).

Rechte

  • et_pb_is_allowed – 1, untyp. (includes/builder/core.php:1136) mit theme_customizer bzw. custom_fonts_management.

Caches

  • ET_Core_PageResource::remove_static_resources – 2 oder 3 (von 7), untyp. (core/components/PageResource.php:1398).
  • et_pb_force_regenerate_templates – 0 (includes/builder/core.php:3811).

Schriftlisten

  • et_builder_get_fonts – 0, untyp. (core.php:4730).
  • et_builder_get_google_fonts – 0 (core.php:4911; Seiteneffekt HTTP, §0.7).
  • et_builder_get_websafe_fonts – 0 (core.php:4783).
  • et_builder_get_custom_fonts – 0 (core.php:4822).
  • optional et_core_use_google_fonts – 0 (core/functions.php:223), et_builder_old_fonts_mapping – 0 (core.php:4839).

Font-Upload

  • ET\Builder\VisualBuilder\Fonts\FontsUtility::font_add – 4, typisiert (array, string, array, array = []), Rückgabe untyp. (FontsUtility.php:102).
  • ET\Builder\VisualBuilder\Fonts\FontsUtility::font_remove – 1, (string), Rückgabe untyp. (:242).
  • ET\Builder\VisualBuilder\Fonts\FontsUtility::mime_types_font – 0, (): array (:290).
  • Konstante FontsUtility::FONT_NAME_MAX_LENGTH (:39).
  • ET\Builder\VisualBuilder\Hooks\HooksRegistration::check_filetype_and_ext_font – 3, (array, string, string): array (HooksRegistration.php:77).
  • et_pb_get_supported_font_formats – 0 (includes/builder/core.php:5090).
  • Hooks (nur Namen, Keys): divi_visual_builder_fonts_custom_font_added, upload_dir/wp_check_filetype_and_ext (von Divi gesetzt).