Ajouter les réseaux sociaux au menu public

This commit is contained in:
Alexandre NOEL 2026-10-11 23:33:50 +02:00
commit 9e05196440
8 changed files with 67 additions and 13 deletions

View file

@ -2,6 +2,15 @@
Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/).
## [1.0.0-rc.33] — 2026-10-11
### Réseaux sociaux dans le menu public
- ajout des icônes des réseaux configurés immédiatement après le lien Contact ;
- masquage automatique des réseaux sans adresse renseignée ;
- ouverture sécurisée dans un nouvel onglet et libellés accessibles aux lecteurs d’écran ;
- réutilisation d’un catalogue unique entre le menu et la page Contact.
## [1.0.0-rc.32] — 2026-10-02
### Statistiques par mois et par période

View file

@ -10,6 +10,10 @@ L’import depuis Animal Shelter Manager a été conçu comme un parcours pruden
Avant la version 1.0, le projet a fait l’objet d’un nettoyage structurel et d’un découpage de ses fichiers les plus volumineux. La recherche universelle, les salles configurables, les traductions française et anglaise et une suite de contrôles isolés ont complété cette phase de stabilisation.
## `1.0.0-rc.33` — Réseaux visibles dès la navigation
Le site public reprend désormais dans son menu les réseaux sociaux renseignés par l’association. Les icônes restent discrètes, accessibles et cohérentes avec les liens déjà présentés sur la page Contact.
## `1.0.0-rc.32` — Statistiques à la période souhaitée
Les bilans ne sont plus limités à une année complète. Une association peut désormais analyser un mois précis ou choisir librement deux dates ; les volumes d’activité, profils, coûts vétérinaires et comparaisons utilisent tous le même intervalle.

View file

@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
## État du projet
La candidate actuelle est `1.0.0-rc.32`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
La candidate actuelle est `1.0.0-rc.33`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md).

View file

@ -1 +1 @@
1.0.0-rc.32
1.0.0-rc.33

View file

@ -31,10 +31,10 @@
<div class="col-lg-6">
<section class="card h-100">
<div class="card-body">
<span class="badge bg-blue-lt mb-2">1.0.0-rc.32</span>
<h3>Statistiques sur mesure</h3>
<p class="text-muted">Bilans par année, par mois ou entre deux dates, avec coûts vétérinaires et
comparaison de la période précédente.</p>
<span class="badge bg-blue-lt mb-2">1.0.0-rc.33</span>
<h3>Un site public mieux connecté</h3>
<p class="text-muted">Les réseaux sociaux renseignés par l’association rejoignent la navigation,
avec des liens discrets, sécurisés et accessibles.</p>
</div>
</section>
</div>

View file

@ -14,12 +14,7 @@ $obfuscate = static fn (string$value): string => implode('', array_reverse(mb_st
class="contact-obfuscated"><?= h($obfuscate($settings['association_email'])) ?></span></div>
<?php endif; ?></div>
<div class="social-links">
<?php $socialNetworks = [
'facebook' => ['Facebook','f'], 'instagram' => ['Instagram','◎'], 'youtube' => ['YouTube','▶'],
'tiktok' => ['TikTok','♪'], 'whatsapp' => ['WhatsApp','☎'], 'linkedin' => ['LinkedIn','in'],
'x' => ['X','𝕏'], 'snapchat' => ['Snapchat','👻'],
];
foreach ($socialNetworks as $network => [$label,$icon]):
<?php foreach ($socialNetworks as $network => [$label,$icon]):
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
if ($url === '') continue; ?>
<a class="social-link social-<?= h($network) ?>" href="<?= h($url) ?>" target="_blank" rel="noopener noreferrer"><span class="social-icon" aria-hidden="true"><?= h($icon) ?></span><span><?= h($label) ?></span></a>

View file

@ -13,6 +13,11 @@ $activeSpecies = (string)($selectedSpecies ?? ($animal['species'] ?? ''));
$navClass = static fn (bool$active): string => $active ? ' class="active" aria-current="page"' : '';
$fullAddress = implode(', ', array_filter([$settings['association_address'] ?? '',trim(($settings['association_postal_code'] ?? '') . ' ' . ($settings['association_city'] ?? ''))]));
$hasContactMap = $activePage === 'contact' && is_numeric($settings['association_latitude'] ?? null) && is_numeric($settings['association_longitude'] ?? null);
$socialNetworks = [
'facebook' => ['Facebook','f'], 'instagram' => ['Instagram','◎'], 'youtube' => ['YouTube','▶'],
'tiktok' => ['TikTok','♪'], 'whatsapp' => ['WhatsApp','☎'], 'linkedin' => ['LinkedIn','in'],
'x' => ['X','𝕏'], 'snapchat' => ['Snapchat','👻'],
];
if (!function_exists('public_age')) {
function public_age(array$a): string
{
@ -170,6 +175,32 @@ if (!function_exists('public_age')) {
color: var(--accent-dark)
}
.nav .nav-social-link {
width: 36px;
height: 36px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 36px;
color: #fff;
font-size: .9rem;
font-weight: 800;
line-height: 1;
box-shadow: 0 3px 9px rgba(0, 0, 0, .12)
}
.nav .nav-social-link:hover,
.nav .nav-social-link:focus-visible {
color: #fff;
filter: brightness(.9);
transform: translateY(-1px)
}
.nav .nav-social-snapchat,
.nav .nav-social-snapchat:hover,
.nav .nav-social-snapchat:focus-visible { color: #111 }
.nav a.active {
font-weight: 800
}
@ -493,7 +524,12 @@ if (!function_exists('public_age')) {
<a<?= $navClass($activePage === 'donation') ?> href="<?= h($siteUrl('donation')) ?>">
<?= h(t('public_site.donation')) ?></a>
<a<?= $navClass($activePage === 'contact') ?> href="<?= h($siteUrl('contact')) ?>">
<?= h(t('public_site.contact')) ?></a>
<?= h(t('public_site.contact')) ?></a><?php foreach ($socialNetworks as $network => [$label,$icon]):
$socialUrl = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
if ($socialUrl === '') continue; ?>
<a class="nav-social-link nav-social-<?= h($network) ?> social-<?= h($network) ?>"
href="<?= h($socialUrl) ?>" target="_blank" rel="noopener noreferrer"
aria-label="<?= h($label) ?>" title="<?= h($label) ?>"><span aria-hidden="true"><?= h($icon) ?></span></a><?php endforeach; ?>
</nav>
</div>
</header>

View file

@ -296,6 +296,16 @@ try {
$externalImage = PublicContentFormatter::render('![Piège](https://example.org/image.webp)');
$assert(!str_contains($externalImage, '<img'), 'Une image externe a été acceptée');
});
$test('Le menu public affiche uniquement les réseaux sociaux configurés', function () use ($assert): void {
$layout = file_get_contents(__DIR__ . '/../app/Views/public_site_layout.php');
$contact = file_get_contents(__DIR__ . '/../app/Views/public_site_contact.php');
$assert(str_contains($layout, '$socialNetworks = ['), 'Le catalogue des réseaux sociaux est absent');
$assert(str_contains($layout, "public_site_' . \$network . '_url"), 'Les liens sociaux ne viennent pas des paramètres');
$assert(str_contains($layout, 'class="nav-social-link'), 'Les icônes sociales sont absentes du menu');
$assert(str_contains($layout, 'aria-label="<?= h($label) ?>"'), 'Les icônes sociales ne sont pas accessibles');
$assert(str_contains($layout, 'rel="noopener noreferrer"'), 'Les liens externes ne sont pas protégés');
$assert(!str_contains($contact, '$socialNetworks = ['), 'Le catalogue social est encore dupliqué');
});
$test('Authentification et mot de passe haché', function () use ($db, $assert): void {
$assert(ini_get('session.use_strict_mode') === '1', 'Le mode strict des sessions est désactivé');
$hash = password_hash('MotDePasseSolide!', PASSWORD_DEFAULT);