. Used by both the wp_head pre-paint * snippet (Welcoma_Frontend::render_prepaint_snippet(), Issue H2/H18) * and can be read by JS via the localized 'class_map' key, so the two * never drift apart (Section 3.2 shared mechanism). * * Deliberately excludes: font-size (needs per-element --welcoma-el-fs * measured at runtime), and monochrome/saturation/brightness/color-invert * (composed into a single inline `filter` string alongside zoom/contrast — * see applyFilters() in welcoma-widget.js). Those stay JS-only. * * @return array */ public static function prepaint_class_map() { return array( 'hide-images' => 'welcoma-hide-images', 'highlight-links' => 'welcoma-highlight-links', 'readable-font' => 'welcoma-readable-font', 'dyslexic-font' => 'welcoma-dyslexic-font', 'font-weight' => 'welcoma-font-weight', 'cognitive-load' => 'welcoma-cognitive-load', 'highlight-titles' => 'welcoma-highlight-titles', 'highlight-content' => 'welcoma-highlight-content', 'stop-animations' => 'welcoma-stop-animations', 'keyboard-nav' => 'welcoma-keyboard-nav', 'focus-highlight' => 'welcoma-focus-highlight', 'line-height' => 'welcoma-line-height', 'letter-spacing' => 'welcoma-letter-spacing', ); } /** * Module ids (with level) assigned to each built-in profile. * * Single source of truth for a *free* profile's module preset — * consumed by both admin/js/welcoma-modules.js and * public/js/welcoma-widget.js via the 'profile_module_map' key in * their localized data, and by default_settings() below for the * PHP-side activation seed. Update only here when a free profile's * module set or level changes. * * Additional profile presets can be supplied by separately installed * add-ons through the 'welcoma_profile_module_map' filter. Core does not * implement those profiles itself. * * @return array> */ public static function builtin_profile_module_map() { $map = array( 'profile-seizure-safe' => array( 'stop-animations' => 1, 'mute-sounds' => 1, 'saturation' => 1, ), 'profile-vision-impaired' => array( 'font-size' => 2, 'contrast' => 1, 'highlight-links' => 1, 'big-cursor' => 1, ), 'profile-low-vision' => array( 'font-size' => 3, 'contrast' => 2, 'highlight-links' => 1, 'big-cursor' => 1, 'highlight-titles' => 1, 'text-magnifier' => 1, ), 'profile-dyslexia-friendly' => array( 'dyslexic-font' => 1, 'line-height' => 2, 'letter-spacing' => 2, 'reading-line' => 1, 'align-text' => 1, ), 'profile-color-vision-support' => array( 'daltonize' => 2, 'highlight-links' => 1, ), ); /** * Filters in the Pro-only profile presets. Welcoma Pro hooks this * to supply the module map for its inbuilt profiles. * * @param array> $pro_map Empty by default (Pro not active/installed). */ $pro_map = apply_filters( 'welcoma_profile_module_map', array() ); if ( is_array( $pro_map ) ) { $map = array_merge( $map, $pro_map ); } return $map; } /** * Default settings used on first install. * * @return array */ public static function default_settings() { $modules = welcoma_widget_get_modules(); $enabled = array(); foreach ( $modules as $module ) { $enabled[ $module['id'] ] = ! empty( $module['default_on'] ); } // Cascade default-on profiles into their assigned modules, so a // fresh install's stored 'enabled' state already matches what the // frontend widget applies for that profile — instead of only being // reconciled client-side on first page load. $profile_map = self::builtin_profile_module_map(); foreach ( $modules as $module ) { if ( ( $module['type'] ?? '' ) !== 'profile' || empty( $module['default_on'] ) ) { continue; } $set = $profile_map[ $module['id'] ] ?? array(); foreach ( array_keys( $set ) as $mid ) { $mod = null; foreach ( $modules as $candidate ) { if ( $candidate['id'] === $mid ) { $mod = $candidate; break; } } if ( ! $mod ) { continue; // Unknown module. } $enabled[ $mid ] = true; } } $defaults = array( 'enabled' => $enabled, 'widget_enabled' => true, 'position' => 'right', // Welcoma-Marken-Defaults (Pine/Cream/Coral; Launcher Pine + Schild-Icon). 'main_icon_bg' => '#0c5a4e', 'icon_color' => '#ffffff', 'widget_icon' => 'accessibility1', 'icon_size' => 60, 'horizontal_offset' => 20, 'vertical_offset' => 20, 'version' => WELCOMA_WIDGET_VERSION, 'header_bg' => '#06302a', 'header_text_color' => '#fbfaf6', 'header_icon_bg' => '#fbfaf6', 'header_icon_color' => '#0c5a4e', 'box_bg_color' => '#fbfaf6', 'box_text_color' => '#14211e', 'module_bg' => '#ffffff', 'module_text_color' => '#14211e', 'module_icon_color' => '#0c5a4e', 'module_icon_bg_color' => '#e7f1ec', 'module_border_color' => '#e7e4da', 'act_module_bg' => '#0c5a4e', 'act_module_text_color' => '#fbfaf6', 'act_module_icon_color' => '#fbfaf6', 'act_module_icon_bg_color' => '#0a4a40', 'act_module_border_color' => '#0c5a4e', 'footer_bg' => '#fbfaf6', 'footer_text_color' => '#14211e', 'reset_btn_bg' => '#ec5a30', 'reset_btn_text_color' => '#ffffff', 'use_website_font' => false, 'default_language' => 'en', 'enabled_languages' => array(), ); return $defaults; } /** * Get the saved settings merged over defaults. * * @return array */ public static function get() { $saved = get_option( WELCOMA_WIDGET_OPTION, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $settings = wp_parse_args( $saved, self::default_settings() ); $settings['enabled'] = wp_parse_args( isset( $saved['enabled'] ) && is_array( $saved['enabled'] ) ? $saved['enabled'] : array(), self::default_settings()['enabled'] ); return $settings; } /** * Sanitize a raw settings payload before saving. * * @param array $raw Untrusted input. * @return array */ public static function sanitize( $raw ) { $clean = self::default_settings(); $saved_data = get_option( WELCOMA_WIDGET_OPTION, array() ); if ( ! is_array( $saved_data ) ) { $saved_data = array(); } $valid = wp_list_pluck( welcoma_widget_get_modules(), 'id' ); // Per-module enabled flags. The free plugin performs no license // check and no Pro/free distinction here — whatever the client // (admin.js) sends for a module id is saved as-is. Any Pro module // that actually needs a license only exists in the catalogue when // the Pro plugin registers it via 'welcoma_widget_modules', and // the Pro plugin is solely responsible for whether that module's // effect actually runs on the frontend. $enabled = array(); $raw_en = isset( $raw['enabled'] ) && is_array( $raw['enabled'] ) ? $raw['enabled'] : array(); foreach ( $valid as $id ) { $enabled[ $id ] = ! empty( $raw_en[ $id ] ); } $clean['enabled'] = $enabled; $clean['widget_enabled'] = ! empty( $raw['widget_enabled'] ); $clean['position'] = ( isset( $raw['position'] ) && 'left' === $raw['position'] ) ? 'left' : 'right'; $clean['main_icon_bg'] = isset( $raw['main_icon_bg'] ) ? sanitize_hex_color( $raw['main_icon_bg'] ) : '#0c5a4e'; $clean['widget_icon'] = isset( $raw['widget_icon'] ) ? sanitize_key( $raw['widget_icon'] ) : 'accessibility1'; // „welcoma" (Bogen-Button-Icon) wurde entfernt → auf Standard migrieren. if ( 'welcoma' === $clean['widget_icon'] ) { $clean['widget_icon'] = 'accessibility1'; } $clean['icon_size'] = isset( $raw['icon_size'] ) ? absint( $raw['icon_size'] ) : 60; $clean['horizontal_offset'] = isset( $raw['horizontal_offset'] ) ? absint( $raw['horizontal_offset'] ) : 50; $clean['vertical_offset'] = isset( $raw['vertical_offset'] ) ? absint( $raw['vertical_offset'] ) : 50; $clean['icon_color'] = isset( $raw['icon_color'] ) ? sanitize_hex_color( $raw['icon_color'] ) : '#ffffff'; $clean['version'] = WELCOMA_WIDGET_VERSION; $clean['header_bg'] = isset( $raw['header_bg'] ) ? sanitize_hex_color( $raw['header_bg'] ) : '#06302a'; $clean['header_text_color'] = isset( $raw['header_text_color'] ) ? sanitize_hex_color( $raw['header_text_color'] ) : '#fbfaf6'; $clean['header_icon_bg'] = isset( $raw['header_icon_bg'] ) ? sanitize_hex_color( $raw['header_icon_bg'] ) : '#fbfaf6'; $clean['header_icon_color'] = isset( $raw['header_icon_color'] ) ? sanitize_hex_color( $raw['header_icon_color'] ) : '#0c5a4e'; $clean['box_bg_color'] = isset( $raw['box_bg_color'] ) ? sanitize_hex_color( $raw['box_bg_color'] ) : '#fbfaf6'; $clean['box_text_color'] = isset( $raw['box_text_color'] ) ? sanitize_hex_color( $raw['box_text_color'] ) : '#14211e'; $clean['module_bg'] = isset( $raw['module_bg'] ) ? sanitize_hex_color( $raw['module_bg'] ) : '#ffffff'; $clean['module_text_color'] = isset( $raw['module_text_color'] ) ? sanitize_hex_color( $raw['module_text_color'] ) : '#14211e'; $clean['module_icon_color'] = isset( $raw['module_icon_color'] ) ? sanitize_hex_color( $raw['module_icon_color'] ) : '#0c5a4e'; $clean['module_icon_bg_color'] = isset( $raw['module_icon_bg_color'] ) ? sanitize_hex_color( $raw['module_icon_bg_color'] ) : '#e7f1ec'; $clean['module_border_color'] = isset( $raw['module_border_color'] ) ? sanitize_hex_color( $raw['module_border_color'] ) : '#e7e4da'; $clean['act_module_bg'] = isset( $raw['act_module_bg'] ) ? sanitize_hex_color( $raw['act_module_bg'] ) : '#0c5a4e'; $clean['act_module_text_color'] = isset( $raw['act_module_text_color'] ) ? sanitize_hex_color( $raw['act_module_text_color'] ) : '#fbfaf6'; $clean['act_module_icon_color'] = isset( $raw['act_module_icon_color'] ) ? sanitize_hex_color( $raw['act_module_icon_color'] ) : '#fbfaf6'; $clean['act_module_icon_bg_color'] = isset( $raw['act_module_icon_bg_color'] ) ? sanitize_hex_color( $raw['act_module_icon_bg_color'] ) : '#0a4a40'; $clean['act_module_border_color'] = isset( $raw['act_module_border_color'] ) ? sanitize_hex_color( $raw['act_module_border_color'] ) : '#0c5a4e'; $clean['footer_bg'] = isset( $raw['footer_bg'] ) ? sanitize_hex_color( $raw['footer_bg'] ) : '#fbfaf6'; $clean['footer_text_color'] = isset( $raw['footer_text_color'] ) ? sanitize_hex_color( $raw['footer_text_color'] ) : '#14211e'; $clean['reset_btn_bg'] = isset( $raw['reset_btn_bg'] ) ? sanitize_hex_color( $raw['reset_btn_bg'] ) : '#ec5a30'; $clean['reset_btn_text_color'] = isset( $raw['reset_btn_text_color'] ) ? sanitize_hex_color( $raw['reset_btn_text_color'] ) : '#ffffff'; $clean['use_website_font'] = ! empty( $raw['use_website_font'] ); // Enabled languages (empty array = all languages shown). // 'en' is valid too — it isn't a key in Welcoma_Translator::LANGUAGES // (which only lists translation targets), but it's still a real, // selectable frontend language. $valid_langs = array_merge( array( 'en' ), array_keys( Welcoma_Translator::LANGUAGES ) ); $clean['enabled_languages'] = array(); if ( isset( $raw['enabled_languages'] ) && is_array( $raw['enabled_languages'] ) ) { foreach ( $raw['enabled_languages'] as $lang ) { $lang = sanitize_text_field( $lang ); if ( in_array( $lang, $valid_langs, true ) ) { $clean['enabled_languages'][] = $lang; } } } // Default language must be one of the supported languages; anything else falls back to English. $default_lang = isset( $raw['default_language'] ) ? sanitize_text_field( $raw['default_language'] ) : ''; $clean['default_language'] = in_array( $default_lang, $valid_langs, true ) ? $default_lang : 'en'; return $clean; } /** * Persist settings. * * @param array $settings Already-sanitized settings. */ public static function save( $settings ) { update_option( WELCOMA_WIDGET_OPTION, $settings ); } /** * Backfill any module registered in welcoma_widget_get_modules() that is * missing from the saved 'enabled' array — e.g. a module added after this * site's settings were first saved, so it was never part of the option. * New keys default to the module's own 'default_on' flag. * * Also re-applies the built-in profile cascade (see default_settings()) * against any profile the site already has enabled: admin.js only * cascades a profile's module set into 'enabled' at the moment someone * clicks that profile's toggle, so a module added to a profile's preset * *after* the site already enabled that profile can never be picked up * by that one-time click — it has to be backfilled here instead. * Modules with an explicit saved value (on or off) are never touched. * * Reads, merges, and only writes when something actually changed. Safe to call on every admin_init. * * @return void */ public static function sync_enabled_modules() { $saved = get_option( WELCOMA_WIDGET_OPTION, false ); if ( ! is_array( $saved ) || ! isset( $saved['enabled'] ) || ! is_array( $saved['enabled'] ) ) { return; // Nothing saved yet — first install seeds a complete set already. } $original = $saved['enabled']; $enabled = $original; $modules = welcoma_widget_get_modules(); $changed = false; foreach ( $modules as $module ) { if ( array_key_exists( $module['id'], $enabled ) ) { continue; } $enabled[ $module['id'] ] = ! empty( $module['default_on'] ); $changed = true; } $profile_map = self::builtin_profile_module_map(); foreach ( $modules as $module ) { if ( ( $module['type'] ?? '' ) !== 'profile' || empty( $enabled[ $module['id'] ] ) ) { continue; } $manually_disabled = $saved['manually_disabled_modules'] ?? array(); foreach ( array_keys( $profile_map[ $module['id'] ] ?? array() ) as $mid ) { // Skip only if the admin *explicitly turned this module off* // (tracked separately below) — not merely because a value // already existed. A pre-existing 'false' from the original // default-on backfill is not a deliberate decision and must // still be cascaded on when newly assigned to an active profile. if ( in_array( $mid, $manually_disabled, true ) ) { continue; } if ( empty( $enabled[ $mid ] ) ) { $enabled[ $mid ] = true; $changed = true; } } } if ( ! $changed ) { return; } $saved['enabled'] = $enabled; update_option( WELCOMA_WIDGET_OPTION, $saved ); } }