From b8f55e5609fc14658fa915f437b4ec8b553109da Mon Sep 17 00:00:00 2001 From: Pierre Martin Date: Wed, 23 Sep 2026 00:54:39 +0200 Subject: [PATCH] Abonnement agenda (.ics) aux distributions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/.htaccess | 2 + src/README-distributions.md | 11 ++++ src/calendrier.php | 101 ++++++++++++++++++++++++++++++++++++ src/index.php | 26 ++++++++++ 4 files changed, 140 insertions(+) create mode 100644 src/calendrier.php diff --git a/src/.htaccess b/src/.htaccess index 5913d92..a43a633 100644 --- a/src/.htaccess +++ b/src/.htaccess @@ -4,3 +4,5 @@ ErrorDocument 404 /404.html RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L] + +RewriteRule ^calendrier\.ics$ calendrier.php [L] diff --git a/src/README-distributions.md b/src/README-distributions.md index 2f3eeda..0e329c4 100644 --- a/src/README-distributions.md +++ b/src/README-distributions.md @@ -69,3 +69,14 @@ Le script PHP : - 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". + +## 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. diff --git a/src/calendrier.php b/src/calendrier.php new file mode 100644 index 0000000..098c367 --- /dev/null +++ b/src/calendrier.php @@ -0,0 +1,101 @@ +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); +} diff --git a/src/index.php b/src/index.php index aaa3a63..f5a9ee9 100644 --- a/src/index.php +++ b/src/index.php @@ -324,6 +324,32 @@ $prochaineDistribution = trouverProchaineDistribution($distributions); +
+ + + Ajouter les distributions à mon agenda + +

+ Ou via + Google Agenda + · télécharger le fichier .ics +

+

+ L’agenda se met à jour tout seul si une date change. +

+