Ajouter les réseaux sociaux au menu public
This commit is contained in:
parent
86642095cd
commit
9e05196440
8 changed files with 67 additions and 13 deletions
|
|
@ -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/).
|
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
|
## [1.0.0-rc.32] — 2026-10-02
|
||||||
|
|
||||||
### Statistiques par mois et par période
|
### Statistiques par mois et par période
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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
|
## `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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
|
||||||
|
|
||||||
## État du projet
|
## É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).
|
Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md).
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.0.0-rc.32
|
1.0.0-rc.33
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<section class="card h-100">
|
<section class="card h-100">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.32</span>
|
<span class="badge bg-blue-lt mb-2">1.0.0-rc.33</span>
|
||||||
<h3>Statistiques sur mesure</h3>
|
<h3>Un site public mieux connecté</h3>
|
||||||
<p class="text-muted">Bilans par année, par mois ou entre deux dates, avec coûts vétérinaires et
|
<p class="text-muted">Les réseaux sociaux renseignés par l’association rejoignent la navigation,
|
||||||
comparaison de la période précédente.</p>
|
avec des liens discrets, sécurisés et accessibles.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
class="contact-obfuscated"><?= h($obfuscate($settings['association_email'])) ?></span></div>
|
||||||
<?php endif; ?></div>
|
<?php endif; ?></div>
|
||||||
<div class="social-links">
|
<div class="social-links">
|
||||||
<?php $socialNetworks = [
|
<?php foreach ($socialNetworks as $network => [$label,$icon]):
|
||||||
'facebook' => ['Facebook','f'], 'instagram' => ['Instagram','◎'], 'youtube' => ['YouTube','▶'],
|
|
||||||
'tiktok' => ['TikTok','♪'], 'whatsapp' => ['WhatsApp','☎'], 'linkedin' => ['LinkedIn','in'],
|
|
||||||
'x' => ['X','𝕏'], 'snapchat' => ['Snapchat','👻'],
|
|
||||||
];
|
|
||||||
foreach ($socialNetworks as $network => [$label,$icon]):
|
|
||||||
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
|
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
|
||||||
if ($url === '') continue; ?>
|
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>
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ $activeSpecies = (string)($selectedSpecies ?? ($animal['species'] ?? ''));
|
||||||
$navClass = static fn (bool$active): string => $active ? ' class="active" aria-current="page"' : '';
|
$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'] ?? ''))]));
|
$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);
|
$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')) {
|
if (!function_exists('public_age')) {
|
||||||
function public_age(array$a): string
|
function public_age(array$a): string
|
||||||
{
|
{
|
||||||
|
|
@ -170,6 +175,32 @@ if (!function_exists('public_age')) {
|
||||||
color: var(--accent-dark)
|
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 {
|
.nav a.active {
|
||||||
font-weight: 800
|
font-weight: 800
|
||||||
}
|
}
|
||||||
|
|
@ -493,7 +524,12 @@ if (!function_exists('public_age')) {
|
||||||
<a<?= $navClass($activePage === 'donation') ?> href="<?= h($siteUrl('donation')) ?>">
|
<a<?= $navClass($activePage === 'donation') ?> href="<?= h($siteUrl('donation')) ?>">
|
||||||
<?= h(t('public_site.donation')) ?></a>
|
<?= h(t('public_site.donation')) ?></a>
|
||||||
<a<?= $navClass($activePage === 'contact') ?> href="<?= h($siteUrl('contact')) ?>">
|
<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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
||||||
|
|
@ -296,6 +296,16 @@ try {
|
||||||
$externalImage = PublicContentFormatter::render('');
|
$externalImage = PublicContentFormatter::render('');
|
||||||
$assert(!str_contains($externalImage, '<img'), 'Une image externe a été acceptée');
|
$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 {
|
$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é');
|
$assert(ini_get('session.use_strict_mode') === '1', 'Le mode strict des sessions est désactivé');
|
||||||
$hash = password_hash('MotDePasseSolide!', PASSWORD_DEFAULT);
|
$hash = password_hash('MotDePasseSolide!', PASSWORD_DEFAULT);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue