'papa-kind-treff', 'avatar_lorelei_eyes_variant' => '', 'avatar_lorelei_eyebrows_variant' => '', 'avatar_lorelei_mouth_variant' => '', 'avatar_lorelei_glasses_variant' => '', 'avatar_lorelei_hair_variant' => '', 'avatar_lorelei_beard_variant' => '', 'avatar_lorelei_earrings_variant' => '', ]; } public static function options(): array { return [ 'avatar_lorelei_eyes_variant' => self::variantOptions(24), 'avatar_lorelei_eyebrows_variant' => self::variantOptions(13), 'avatar_lorelei_mouth_variant' => self::mouthVariantOptions(), 'avatar_lorelei_glasses_variant' => ['' => 'Automatisch'] + self::variantOptions(5), 'avatar_lorelei_hair_variant' => ['' => 'Automatisch'] + self::variantOptions(48), 'avatar_lorelei_beard_variant' => ['' => 'Automatisch'] + self::variantOptions(2), 'avatar_lorelei_earrings_variant' => ['' => 'Automatisch'] + self::variantOptions(3), ]; } public static function componentDefinitions(): array { return [ 'avatar_lorelei_eyes_variant' => ['label' => 'Augen', 'auto' => false], 'avatar_lorelei_eyebrows_variant' => ['label' => 'Augenbrauen', 'auto' => false], 'avatar_lorelei_mouth_variant' => ['label' => 'Mund', 'auto' => false], 'avatar_lorelei_glasses_variant' => ['label' => 'Brille', 'auto' => true], 'avatar_lorelei_hair_variant' => ['label' => 'Haare', 'auto' => true], 'avatar_lorelei_beard_variant' => ['label' => 'Bart', 'auto' => true], 'avatar_lorelei_earrings_variant' => ['label' => 'Ohrringe', 'auto' => true], ]; } public static function selectColumns(string $alias = 'p'): string { $columns = array_keys(self::defaults()); return implode(', ', array_map( static fn(string $column): string => sprintf('%s.%s', $alias, $column), $columns )); } public static function normalize(array $input): array { $defaults = self::defaults(); $normalized = []; foreach ($defaults as $key => $defaultValue) { $value = (string)($input[$key] ?? $defaultValue); if ($key === 'avatar_preset') { $normalized[$key] = self::normalizeSeed($value); continue; } if (str_starts_with($key, 'avatar_lorelei_')) { $normalized[$key] = self::normalizeVariant($value); continue; } $normalized[$key] = $value; } return $normalized; } public static function normalizeSeed(string $seed): string { $seed = strtolower(trim($seed)); $seed = preg_replace('/[^a-z0-9]+/', '-', $seed) ?: ''; $seed = trim($seed, '-'); if ($seed === '') { $seed = self::defaults()['avatar_preset']; } return substr($seed, 0, 64); } public static function apiQuery(array $row): array { $avatar = self::normalize($row); $query = ['seed' => $avatar['avatar_preset']]; $map = [ 'avatar_lorelei_eyes_variant' => 'eyesVariant', 'avatar_lorelei_eyebrows_variant' => 'eyebrowsVariant', 'avatar_lorelei_mouth_variant' => 'mouthVariant', 'avatar_lorelei_glasses_variant' => 'glassesVariant', 'avatar_lorelei_hair_variant' => 'hairVariant', 'avatar_lorelei_beard_variant' => 'beardVariant', 'avatar_lorelei_earrings_variant' => 'earringsVariant', ]; foreach ($map as $key => $param) { if ($avatar[$key] !== '') { $query[$param] = $avatar[$key]; } } return $query; } public static function buildProxyUrl(array $row): string { return '/api/avatar?' . http_build_query(self::apiQuery($row)); } public static function previewChoices(array $row, string $field): array { $definitions = self::componentDefinitions(); $options = self::options(); $avatar = self::normalize($row); if (!isset($definitions[$field], $options[$field])) { return []; } $choices = []; foreach ($options[$field] as $value => $label) { $variantRow = $avatar; $variantRow[$field] = (string) $value; $choices[] = [ 'value' => (string) $value, 'label' => (string) $label, 'image' => self::buildProxyUrl($variantRow), 'checked' => $avatar[$field] === (string) $value, ]; } return $choices; } public static function render(array $row, string $name = 'Mitglied', string $size = 'md'): string { $avatar = self::normalize($row); $src = self::buildProxyUrl($avatar); return sprintf( '', htmlspecialchars(implode(' ', [ 'pkt-avatar', 'pkt-avatar--' . preg_replace('/[^a-z0-9\-]/', '', strtolower($size)), 'pkt-avatar--generated', ]), ENT_QUOTES), htmlspecialchars('Avatar von ' . $name, ENT_QUOTES), htmlspecialchars($src, ENT_QUOTES) ); } public static function fallbackSvg(string $seed, string $label = 'Avatar'): string { $seed = self::normalizeSeed($seed); $initials = strtoupper(substr(preg_replace('/[^a-z]/', '', $seed) ?: 'pk', 0, 2)); $palette = ['#f5efe5', '#d9c7b3', '#8f6f54', '#37485a', '#c88f5b']; $hash = abs((int) crc32($seed)); $bg = $palette[$hash % count($palette)]; $fg = $hash % 2 === 0 ? '#243142' : '#ffffff'; return sprintf( '%s', htmlspecialchars($label, ENT_QUOTES), htmlspecialchars($bg, ENT_QUOTES), htmlspecialchars($fg, ENT_QUOTES), htmlspecialchars($initials, ENT_QUOTES) ); } private static function variantOptions(int $count): array { $options = []; for ($index = 1; $index <= $count; $index++) { $key = sprintf('variant%02d', $index); $options[$key] = 'Variante ' . $index; } return $options; } private static function mouthVariantOptions(): array { $options = []; for ($index = 1; $index <= 18; $index++) { $key = sprintf('happy%02d', $index); $options[$key] = 'Freundlich ' . $index; } for ($index = 1; $index <= 9; $index++) { $key = sprintf('sad%02d', $index); $options[$key] = 'Zurueckhaltend ' . $index; } return $options; } public static function normalizeVariant(string $value): string { $value = strtolower(trim($value)); if ($value === '') { return ''; } return preg_match('/^(variant\d{2}|happy\d{2}|sad\d{2})$/', $value) === 1 ? $value : ''; } }