Publier Globinours 1.0.0-rc.3
This commit is contained in:
parent
ea8c24d622
commit
9a2b4068da
325 changed files with 38230 additions and 20 deletions
124
app/Services/CalendarFeedService.php
Normal file
124
app/Services/CalendarFeedService.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
final class CalendarFeedService
|
||||
{
|
||||
public static function generateToken(int $userId): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
DB::pdo()
|
||||
->prepare(
|
||||
"UPDATE users SET agenda_feed_token_hash=:hash,agenda_feed_created_at=datetime('now'),updated_at=datetime('now') WHERE id=:id AND active=1",
|
||||
)
|
||||
->execute([':hash' => hash('sha256', $token), ':id' => $userId]);
|
||||
return $token;
|
||||
}
|
||||
public static function revoke(int $userId): void
|
||||
{
|
||||
DB::pdo()
|
||||
->prepare(
|
||||
"UPDATE users SET agenda_feed_token_hash=NULL,agenda_feed_created_at=NULL,updated_at=datetime('now') WHERE id=?",
|
||||
)
|
||||
->execute([$userId]);
|
||||
}
|
||||
public static function userForToken(string $token): ?array
|
||||
{
|
||||
if (!preg_match('/^[a-f0-9]{64}$/', $token)) {
|
||||
return null;
|
||||
}
|
||||
$s = DB::pdo()->prepare('SELECT id,role FROM users WHERE agenda_feed_token_hash=? AND active=1 LIMIT 1');
|
||||
$s->execute([hash('sha256', $token)]);
|
||||
$user = $s->fetch(PDO::FETCH_ASSOC) ?: null;
|
||||
if (!$user || !PermissionService::can('agenda', 'view', (string) $user['role'])) {
|
||||
return null;
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
public static function absoluteUrl(string $path): string
|
||||
{
|
||||
$configured = rtrim((string) getenv('APP_URL'), '/');
|
||||
if ($configured !== '' && preg_match('~^https?://~i', $configured)) {
|
||||
return $configured . $path;
|
||||
}
|
||||
$proto = (string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '');
|
||||
if (!in_array($proto, ['http', 'https'], true)) {
|
||||
$proto = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http';
|
||||
}
|
||||
$host = (string) ($_SERVER['HTTP_X_FORWARDED_HOST'] ?? ($_SERVER['HTTP_HOST'] ?? 'localhost'));
|
||||
$host = trim(explode(',', $host)[0]);
|
||||
if (!preg_match('/^[a-z0-9.:-]+$/i', $host)) {
|
||||
$host = 'localhost';
|
||||
}
|
||||
return $proto . '://' . $host . $path;
|
||||
}
|
||||
public static function render(array $items, string $name): string
|
||||
{
|
||||
$lines = [
|
||||
'BEGIN:VCALENDAR',
|
||||
'VERSION:2.0',
|
||||
'PRODID:-//Globinours//Agenda refuge//FR',
|
||||
'CALSCALE:GREGORIAN',
|
||||
'METHOD:PUBLISH',
|
||||
'X-WR-CALNAME:' . self::escape($name),
|
||||
'X-WR-TIMEZONE:Europe/Paris',
|
||||
];
|
||||
$stamp = gmdate('Ymd\THis\Z');
|
||||
foreach ($items as $item) {
|
||||
$source = (string) ($item['source_type'] ?? 'agenda');
|
||||
$sourceId = (int) ($item['source_id'] ?? $item['id']);
|
||||
$instance = (string) $item['instance_starts_at'];
|
||||
$uid = $source . '-' . $sourceId . '-' . substr(hash('sha256', $instance), 0, 12) . '@globinours';
|
||||
$lines[] = 'BEGIN:VEVENT';
|
||||
$lines[] = 'UID:' . $uid;
|
||||
$lines[] = 'DTSTAMP:' . $stamp;
|
||||
if ((int) $item['all_day'] === 1) {
|
||||
$start = new DateTimeImmutable(substr($instance, 0, 10));
|
||||
$lines[] = 'DTSTART;VALUE=DATE:' . $start->format('Ymd');
|
||||
$lines[] = 'DTEND;VALUE=DATE:' . $start->modify('+1 day')->format('Ymd');
|
||||
} else {
|
||||
$start = new DateTimeImmutable($instance, new DateTimeZone('Europe/Paris'));
|
||||
$end = !empty($item['instance_ends_at'])
|
||||
? new DateTimeImmutable($item['instance_ends_at'], new DateTimeZone('Europe/Paris'))
|
||||
: $start->modify('+1 hour');
|
||||
$lines[] = 'DTSTART;TZID=Europe/Paris:' . $start->format('Ymd\THis');
|
||||
$lines[] = 'DTEND;TZID=Europe/Paris:' . $end->format('Ymd\THis');
|
||||
}
|
||||
$lines[] = 'SUMMARY:' . self::escape((string) $item['title']);
|
||||
$description = trim((string) ($item['description'] ?? ''));
|
||||
if (!empty($item['animal_name'])) {
|
||||
$description = trim($description . "\n" . t('animal.animal') . ' : ' . $item['animal_name']);
|
||||
}
|
||||
if ($description !== '') {
|
||||
$lines[] = 'DESCRIPTION:' . self::escape($description);
|
||||
}
|
||||
if (!empty($item['location'])) {
|
||||
$lines[] = 'LOCATION:' . self::escape((string) $item['location']);
|
||||
}
|
||||
if (!empty($item['animal_id'])) {
|
||||
$lines[] = 'URL:' . self::escape(self::absoluteUrl('/animal?id=' . (int) $item['animal_id']));
|
||||
}
|
||||
$lines[] = 'STATUS:' . (($item['status'] ?? 'planned') === 'completed' ? 'COMPLETED' : 'CONFIRMED');
|
||||
$lines[] = 'END:VEVENT';
|
||||
}
|
||||
$lines[] = 'END:VCALENDAR';
|
||||
return implode("\r\n", array_map([self::class, 'fold'], $lines)) . "\r\n";
|
||||
}
|
||||
private static function escape(string $value): string
|
||||
{
|
||||
return str_replace(['\\', "\r\n", "\n", "\r", ';', ','], ['\\\\', '\\n', '\\n', '\\n', '\\;', '\\,'], $value);
|
||||
}
|
||||
private static function fold(string $line): string
|
||||
{
|
||||
$out = '';
|
||||
while (strlen($line) > 73) {
|
||||
$cut = 73;
|
||||
while ($cut > 0 && (ord($line[$cut]) & 0xc0) === 0x80) {
|
||||
$cut--;
|
||||
}
|
||||
$out .= substr($line, 0, $cut) . "\r\n ";
|
||||
$line = substr($line, $cut);
|
||||
}
|
||||
return $out . $line;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue