Abonnement agenda (.ics) aux distributions

Un flux iCalendar généré depuis distributions.php, servi sur
/calendrier.ics, permet d'ajouter toutes les distributions à venir à son
agenda (iPhone, Android, Google Agenda, Outlook). En abonnement webcal://,
les agendas suivent les changements de dates sans rien réimporter.

Les heures sont converties depuis le champ `horaires` en tenant compte du
changement d'heure ; les distributions annulées sont exclues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre Martin
2026-09-23 00:54:39 +02:00
co-authored by Claude Opus 5.5
parent b705f25eff
commit b8f55e5609
4 changed files with 140 additions and 0 deletions
+2
View File
@@ -4,3 +4,5 @@ ErrorDocument 404 /404.html
RewriteCond %{HTTPS} off RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L] RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L]
RewriteRule ^calendrier\.ics$ calendrier.php [L]
+11
View File
@@ -69,3 +69,14 @@ Le script PHP :
- Les horaires propres à chaque date (la Guinguette finit plus tard) - Les horaires propres à chaque date (la Guinguette finit plus tard)
Les dates sont triées chronologiquement et la première date future devient automatiquement "Prochaine distribution". Les dates sont triées chronologiquement et la première date future devient automatiquement "Prochaine distribution".
## Abonnement agenda (.ics)
`calendrier.php` (servi à l'adresse `/calendrier.ics` grâce au `.htaccess`)
génère un flux iCalendar à partir de `distributions.php`. Les personnes
abonnées depuis le bouton « Ajouter les distributions à mon agenda » voient
les modifications arriver automatiquement : il suffit de mettre à jour
`distributions.php`. Les distributions annulées ne figurent pas dans le flux.
Les horaires doivent rester au format `17h-19h` ou `17h-21h30` pour pouvoir
être convertis en heures de début et de fin.
+101
View File
@@ -0,0 +1,101 @@
<?php
/**
* Flux iCalendar (.ics) des distributions, généré depuis distributions.php.
*
* Utilisable en abonnement (webcal://) : les agendas se mettent à jour
* automatiquement quand le fichier de configuration change.
*/
require_once __DIR__ . '/distributions-utils.php';
/**
* Convertit une date et une heure au format '17h' ou '21h30' en horodatage UTC iCalendar
*/
function heureVersIcs(string $date, string $heure): string
{
if (!preg_match('/^(\d{1,2})h(\d{2})?$/', trim($heure), $m)) {
throw new InvalidArgumentException("Horaire invalide : $heure");
}
$dateHeure = new DateTime($date, new DateTimeZone('Europe/Paris'));
$dateHeure->setTime((int) $m[1], (int) ($m[2] ?? 0));
$dateHeure->setTimezone(new DateTimeZone('UTC'));
return $dateHeure->format('Ymd\THis\Z');
}
/**
* Échappe un texte pour une valeur iCalendar
*/
function echapperIcs(string $texte): string
{
return str_replace(['\\', ';', ',', "\n"], ['\\\\', '\;', '\,', '\n'], $texte);
}
/**
* Replie une ligne à 75 octets maximum, sans couper un caractère multi-octets
*/
function plierLigneIcs(string $ligne): string
{
$resultat = '';
$courante = '';
foreach (mb_str_split($ligne) as $caractere) {
if (strlen($courante) + strlen($caractere) > 75) {
$resultat .= $courante . "\r\n";
$courante = ' ';
}
$courante .= $caractere;
}
return $resultat . $courante . "\r\n";
}
$lignes = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Les Frouzivores//Distributions//FR',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH',
'X-WR-CALNAME:Distributions Les Frouzivores',
'X-WR-TIMEZONE:Europe/Paris',
'REFRESH-INTERVAL;VALUE=DURATION:P1D',
'X-PUBLISHED-TTL:P1D',
];
$maintenant = gmdate('Ymd\THis\Z');
foreach (chargerDistributions() as $distribution) {
if ($distribution['type'] === 'annulee') {
continue;
}
[$debut, $fin] = explode('-', $distribution['horaires']);
$lieu = getLieuInfo($distribution['lieu']);
$titre = 'Distribution Les Frouzivores';
if (!empty($distribution['label'])) {
$titre .= ' ' . $distribution['label'];
}
$lignes[] = 'BEGIN:VEVENT';
$lignes[] = 'UID:distribution-' . $distribution['date'] . '@lesfrouzivores.fr';
$lignes[] = 'DTSTAMP:' . $maintenant;
$lignes[] = 'DTSTART:' . heureVersIcs($distribution['date'], $debut);
$lignes[] = 'DTEND:' . heureVersIcs($distribution['date'], $fin);
$lignes[] = 'SUMMARY:' . echapperIcs($titre);
if ($lieu) {
$lignes[] = 'LOCATION:' . echapperIcs($lieu['nom'] . ', ' . $lieu['adresse'] . ', 31270 Frouzins');
}
$lignes[] = 'DESCRIPTION:' . echapperIcs(
"Distribution mensuelle des commandes passées sur local.direct.\n"
. 'Commander : https://www.local.direct/les-frouzivores'
);
$lignes[] = 'URL:https://lesfrouzivores.fr/';
$lignes[] = 'END:VEVENT';
}
$lignes[] = 'END:VCALENDAR';
header('Content-Type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename="frouzivores-distributions.ics"');
foreach ($lignes as $ligne) {
echo plierLigneIcs($ligne);
}
+26
View File
@@ -324,6 +324,32 @@ $prochaineDistribution = trouverProchaineDistribution($distributions);
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div class="mt-8 text-center">
<a
href="webcal://lesfrouzivores.fr/calendrier.ics"
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-green-600 hover:bg-green-700"
>
<i class="fas fa-calendar-plus mr-2"></i>
Ajouter les distributions à mon agenda
</a>
<p class="mt-3 text-sm text-gray-500">
Ou via
<a
href="https://calendar.google.com/calendar/render?cid=webcal://lesfrouzivores.fr/calendrier.ics"
class="text-green-700 underline hover:text-green-800"
>Google Agenda</a
>
· <a
href="/calendrier.ics"
download="frouzivores-distributions.ics"
class="text-green-700 underline hover:text-green-800"
>télécharger le fichier .ics</a
>
</p>
<p class="mt-1 text-xs text-gray-400">
Lagenda se met à jour tout seul si une date change.
</p>
</div>
</div> </div>
</div> </div>