219 lines
7.6 KiB
PHP
219 lines
7.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Avatar;
|
|
|
|
final class Lorelei
|
|
{
|
|
public static function defaults(): array
|
|
{
|
|
return [
|
|
'avatar_preset' => '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(
|
|
'<span class="%s" role="img" aria-label="%s"><img class="pkt-avatar__img" src="%s" alt=""></span>',
|
|
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(
|
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="%s"><rect width="128" height="128" rx="28" fill="%s"/><circle cx="64" cy="50" r="24" fill="rgba(255,255,255,0.26)"/><path d="M30 110c4-18 18-30 34-30s30 12 34 30" fill="rgba(255,255,255,0.2)"/><text x="64" y="76" text-anchor="middle" font-family="Arial, sans-serif" font-size="28" font-weight="700" fill="%s">%s</text></svg>',
|
|
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 : '';
|
|
}
|
|
}
|