This commit is contained in:
@@ -2,7 +2,7 @@ Anweisung: Projektstruktur (Basis-Template) wie in „papa-kind-treff“
|
||||
|
||||
Aktueller Projektstand
|
||||
- Papa-Kind-Treff nutzt im Mitgliederbereich DiceBear `lorelei` über einen internen API-Proxy mit lokalem Cache.
|
||||
- Relevante Logik liegt in `src/App/Avatar.php`.
|
||||
- Relevante Logik liegt in `src/App/Avatar/Lorelei.php`.
|
||||
- Der Avatar-Proxy liegt unter `public/page/api/avatar.php`.
|
||||
- Änderungen an Funktionen mit Cookies, LocalStorage, SessionStorage, Geolocation, Tracking oder Drittanbietern müssen immer auch in Datenschutz-/Cookie-Hinweisen und im Consent-Flow berücksichtigt werden.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter. Kernbereiche sind l
|
||||
- Geschäftslogik unter `src/App/`
|
||||
- MySQL/MariaDB-Schema in `schema.sql`
|
||||
- Globales Frontend vor allem über `public/assets/js/app.js` und `public/assets/css/app.css`
|
||||
- Avatar-Style `Lorelei` liegt getrennt unter `src/App/Avatar/Lorelei.php`
|
||||
|
||||
## Wichtige Produktentscheidungen
|
||||
- Die Ansprache im Frontend soll immer den aktuellen Nutzer direkt ansprechen.
|
||||
|
||||
@@ -37,6 +37,7 @@ Papa-Kind-Treff ist eine PHP-basierte Plattform für Väter mit Fokus auf lokale
|
||||
- `owner`
|
||||
|
||||
Die Rechteverteilung und Community-Logik sind in `src/App/CommunityAccess.php` abgebildet.
|
||||
Die Lorelei-Avatarlogik liegt in `src/App/Avatar/Lorelei.php`.
|
||||
|
||||
## Consent und rechtlich relevante Integrationen
|
||||
Der aktuelle Code nutzt oder kann nutzen:
|
||||
|
||||
@@ -63,7 +63,7 @@ $sectionLinks = [
|
||||
<div class="account-panel__body">
|
||||
<div class="profile-avatar-panel">
|
||||
<div class="profile-avatar-panel__preview">
|
||||
<?= \App\Avatar::render($profile, $profile['display_name'] ?: 'Papa', 'xl') ?>
|
||||
<?= \App\Avatar\Lorelei::render($profile, $profile['display_name'] ?: 'Papa', 'xl') ?>
|
||||
</div>
|
||||
<div class="profile-avatar-panel__content">
|
||||
<h3>Dein Profilbild</h3>
|
||||
@@ -300,7 +300,7 @@ $sectionLinks = [
|
||||
<div class="avatar-builder__layout">
|
||||
<div class="avatar-builder__preview">
|
||||
<div data-avatar-preview>
|
||||
<?= \App\Avatar::render($profile, $profile['display_name'] ?: 'Papa', 'xxl') ?>
|
||||
<?= \App\Avatar\Lorelei::render($profile, $profile['display_name'] ?: 'Papa', 'xxl') ?>
|
||||
</div>
|
||||
<p class="muted small">Wähle eine Lorelei-Variante oder lade dir direkt neue Vorschläge. Die Vorschau aktualisiert sich sofort.</p>
|
||||
</div>
|
||||
|
||||
@@ -115,7 +115,7 @@ $threads = $community
|
||||
?>
|
||||
<article class="forum-row">
|
||||
<div class="forum-row__topic">
|
||||
<div class="forum-row__avatar"><?= \App\Avatar::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
|
||||
<div class="forum-row__avatar"><?= \App\Avatar\Lorelei::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
|
||||
<div>
|
||||
<h3><a href="/community_thread?id=<?= (int)$t['id'] ?>"><?= htmlspecialchars((string)$t['title'], ENT_QUOTES) ?></a></h3>
|
||||
<div class="forum-row__meta">
|
||||
@@ -206,7 +206,7 @@ $threads = $community
|
||||
?>
|
||||
<article class="forum-row">
|
||||
<div class="forum-row__topic">
|
||||
<div class="forum-row__avatar"><?= \App\Avatar::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
|
||||
<div class="forum-row__avatar"><?= \App\Avatar\Lorelei::render($t, (string)($t['display_name'] ?: 'Mitglied'), 'forum-list') ?></div>
|
||||
<div>
|
||||
<h3><a href="/community_thread?id=<?= (int)$t['id'] ?>"><?= htmlspecialchars((string)$t['title'], ENT_QUOTES) ?></a></h3>
|
||||
<div class="forum-row__meta">
|
||||
|
||||
@@ -154,7 +154,7 @@ $userVotes = ($access && $userId) ? $access->getUserPostVotes((int)$userId, $pos
|
||||
|
||||
<article class="forum-post forum-post--lead">
|
||||
<aside class="forum-post__author">
|
||||
<div class="forum-post__avatar"><?= \App\Avatar::render($thread, $threadAuthor, 'forum') ?></div>
|
||||
<div class="forum-post__avatar"><?= \App\Avatar\Lorelei::render($thread, $threadAuthor, 'forum') ?></div>
|
||||
<strong><?= htmlspecialchars($threadAuthor, ENT_QUOTES) ?></strong>
|
||||
<span><?= htmlspecialchars(trim((string)($threadLevel['icon'] ?? '') . ' ' . (string)($threadLevel['label'] ?? '')), ENT_QUOTES) ?></span>
|
||||
<span><?= number_format($threadPoints, 1) ?> Punkte</span>
|
||||
@@ -203,7 +203,7 @@ $userVotes = ($access && $userId) ? $access->getUserPostVotes((int)$userId, $pos
|
||||
?>
|
||||
<article class="forum-post<?= $isHighlighted ? ' forum-post--highlighted' : '' ?>">
|
||||
<aside class="forum-post__author">
|
||||
<div class="forum-post__avatar"><?= \App\Avatar::render($p, $postAuthor, 'forum') ?></div>
|
||||
<div class="forum-post__avatar"><?= \App\Avatar\Lorelei::render($p, $postAuthor, 'forum') ?></div>
|
||||
<strong><?= htmlspecialchars($postAuthor, ENT_QUOTES) ?></strong>
|
||||
<?php if ($isHighlighted): ?>
|
||||
<span class="forum-highlight-badge">Hilfreich hervorgehoben</span>
|
||||
|
||||
@@ -48,7 +48,7 @@ if ($isLoggedIn) {
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<div class="profile-menu" data-profile-menu>
|
||||
<button class="profile-menu__trigger" type="button" aria-haspopup="menu" aria-expanded="false" data-profile-menu-trigger>
|
||||
<span class="profile-menu__avatar" aria-hidden="true"><?= \App\Avatar::render($profileRow ?? [], $displayName, 'nav') ?></span>
|
||||
<span class="profile-menu__avatar" aria-hidden="true"><?= \App\Avatar\Lorelei::render($profileRow ?? [], $displayName, 'nav') ?></span>
|
||||
<span class="profile-menu__name"><?= htmlspecialchars($displayName, ENT_QUOTES) ?></span>
|
||||
</button>
|
||||
<div class="profile-menu__dropdown" role="menu">
|
||||
|
||||
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Avatar\Lorelei;
|
||||
|
||||
final class AccountPages
|
||||
{
|
||||
public static function register(App $app): array
|
||||
@@ -363,7 +365,7 @@ final class AccountPages
|
||||
$profile['contact_phone'] = $crypto->decrypt((string)$profile['contact_phone']) ?: '';
|
||||
}
|
||||
}
|
||||
$profile = array_merge($profile, Avatar::normalize($profile));
|
||||
$profile = array_merge($profile, Lorelei::normalize($profile));
|
||||
|
||||
$children = [];
|
||||
$stmt = $pdo?->prepare('SELECT id, encrypted_first_name AS first_name, note, gender, birthdate, age_years FROM children WHERE user_id = :id ORDER BY id DESC');
|
||||
@@ -420,7 +422,7 @@ final class AccountPages
|
||||
if (!in_array($section, $allowedSections, true)) {
|
||||
$section = 'profile';
|
||||
}
|
||||
$avatarPresetOptions = Avatar::seedChoices((string)$profile['avatar_preset']);
|
||||
$avatarPresetOptions = Lorelei::seedChoices((string)$profile['avatar_preset']);
|
||||
|
||||
return compact(
|
||||
'flash',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
namespace App\Avatar;
|
||||
|
||||
final class Avatar
|
||||
final class Lorelei
|
||||
{
|
||||
public static function defaults(): array
|
||||
{
|
||||
@@ -90,7 +90,7 @@ final class Avatar
|
||||
]];
|
||||
|
||||
$seen = [$selectedSeed => true];
|
||||
$base = abs((int)crc32($selectedSeed));
|
||||
$base = abs((int) crc32($selectedSeed));
|
||||
$index = 0;
|
||||
|
||||
while (count($choices) < $count) {
|
||||
@@ -141,7 +141,7 @@ final class Avatar
|
||||
$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));
|
||||
$hash = abs((int) crc32($seed));
|
||||
$bg = $palette[$hash % count($palette)];
|
||||
$fg = $hash % 2 === 0 ? '#243142' : '#ffffff';
|
||||
|
||||
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Avatar\Lorelei;
|
||||
|
||||
final class Community
|
||||
{
|
||||
private ?array $forumStructure = null;
|
||||
@@ -151,7 +153,7 @@ final class Community
|
||||
$where = $conditions ? ('AND ' . implode(' AND ', $conditions)) : '';
|
||||
|
||||
$avatarSelect = $this->hasColumn('user_profiles', 'avatar_outfit_color')
|
||||
? ', ' . Avatar::selectColumns('p')
|
||||
? ', ' . Lorelei::selectColumns('p')
|
||||
: '';
|
||||
$sql = "SELECT ft.id, ft.title, ft.body, ft.created_at, ft.updated_at,
|
||||
u.id as uid, u.created_at as user_created,
|
||||
@@ -207,7 +209,7 @@ final class Community
|
||||
{
|
||||
$select = 'ft.*, p.display_name';
|
||||
if ($this->hasColumn('user_profiles', 'avatar_outfit_color')) {
|
||||
$select .= ', ' . Avatar::selectColumns('p');
|
||||
$select .= ', ' . Lorelei::selectColumns('p');
|
||||
}
|
||||
$join = '';
|
||||
if ($this->hasColumn('forum_threads', 'board_id') && $this->hasTable('forum_boards') && $this->hasTable('forum_categories')) {
|
||||
@@ -227,7 +229,7 @@ final class Community
|
||||
{
|
||||
$select = 'fp.*, p.display_name';
|
||||
if ($this->hasColumn('user_profiles', 'avatar_outfit_color')) {
|
||||
$select .= ', ' . Avatar::selectColumns('p');
|
||||
$select .= ', ' . Lorelei::selectColumns('p');
|
||||
}
|
||||
if ($this->hasColumn('forum_posts', 'highlighted_at')) {
|
||||
$select .= ', hp.display_name AS highlighted_by_name';
|
||||
|
||||
@@ -3,6 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
use App\Avatar\Lorelei;
|
||||
|
||||
final class ProfileSettings
|
||||
{
|
||||
private bool $schemaEnsured = false;
|
||||
@@ -87,7 +89,7 @@ final class ProfileSettings
|
||||
}
|
||||
|
||||
$this->ensureSchema();
|
||||
$avatar = Avatar::normalize($avatarConfig);
|
||||
$avatar = Lorelei::normalize($avatarConfig);
|
||||
$stmt = $this->pdo->prepare(
|
||||
'UPDATE user_profiles
|
||||
SET avatar_preset = :preset,
|
||||
|
||||
Reference in New Issue
Block a user