Added ics calendar

This commit is contained in:
Tim Lappe 2025-03-13 21:28:47 +01:00
parent 0d4a34c4ad
commit ec166854cb
3 changed files with 203 additions and 1 deletions

71
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "df89229d36c9d3133b8f00872aefe73d",
"content-hash": "3affcf22479997bf7bae2bd6b9c0cf5e",
"packages": [
{
"name": "dflydev/hawk",
@ -893,6 +893,75 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "symfony/asset",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
"reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/asset/zipball/cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
"reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0",
"shasum": ""
},
"require": {
"php": ">=8.2"
},
"conflict": {
"symfony/http-foundation": "<6.4"
},
"require-dev": {
"symfony/http-client": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Asset\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/asset/tree/v7.2.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-10-25T15:15:23+00:00"
},
{
"name": "symfony/cache",
"version": "v7.2.4",

View File

@ -3,6 +3,7 @@
namespace App\Controller;
use App\Service\AbsenceManager;
use App\Service\CalendarExportService;
use App\Service\CalendarService;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -15,6 +16,7 @@ final class CalendarController extends AbstractController
public function __construct(
private readonly AbsenceManager $absenceManager,
private readonly CalendarService $calendarService,
private readonly CalendarExportService $calendarExportService,
private readonly string $requiredApiKey
) {
}
@ -40,4 +42,28 @@ final class CalendarController extends AbstractController
'days' => $days,
]);
}
#[Route('/cal', name: 'calendar_ics')]
public function exportCalendarIcs(Request $request): Response
{
if (!$request->query->has('key') || $request->query->get('key') !== $this->requiredApiKey) {
return new Response('Kein Zugriff', Response::HTTP_FORBIDDEN);
}
$startDate = (new DateTime())->format('Y-m-d');
$endDate = (new DateTime('+180 days'))->format('Y-m-d');
$absences = $this->absenceManager->getAbsencesForUser('tim.lappe@check24.de', new DateTime('-1 day'));
$days = $this->calendarService->getAllDays($startDate, $endDate);
$days = $this->calendarService->processAbsences($days, $absences);
$icsContent = $this->calendarExportService->generateIcsContent($days, $absences);
$response = new Response($icsContent);
$response->headers->set('Content-Type', 'text/calendar; charset=utf-8');
$response->headers->set('Content-Disposition', 'attachment; filename="work_calendar.ics"');
return $response;
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace App\Service;
use App\Model\Absence;
use DateTime;
use DateTimeInterface;
use DateTimeZone;
final class CalendarExportService
{
private const DUSSELDORF_ADDRESS = 'CHECK24 \nHeinricht-Heine-Allee 53\n40213 Düsseldorf\nDeutschland';
private const KAMEN_ADDRESS = 'Privat \nHeidkamp 21\n59174 Kamen\nDeutschland';
private const COMPANY_NAME = 'CHECK24';
private const TIMEZONE = 'Europe/Berlin';
public function generateIcsContent(array $days, array $absences): string
{
$icsContent = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//CHECK24//Work Calendar//EN',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH',
$this->generateTimezone(),
];
foreach ($days as $day) {
if ($this->isWeekend($day) || ($day['isUrlaub'] ?? false)) {
continue;
}
$eventStart = clone $day['date'];
$eventStart->setTime(9, 0, 0);
$eventEnd = clone $day['date'];
$eventEnd->setTime(17, 30, 0);
$isHomeOffice = $day['isFtk'] ?? false;
$location = $isHomeOffice ? self::KAMEN_ADDRESS : self::DUSSELDORF_ADDRESS;
$name = match ($isHomeOffice) {
true => 'CHECK24 🏠',
false => 'CHECK24 🏢',
};
$icsContent[] = 'BEGIN:VEVENT';
$icsContent[] = 'UID:' . $this->generateUid($eventStart);
$icsContent[] = 'DTSTAMP:' . $this->formatDateTime(new DateTime('now', new DateTimeZone(self::TIMEZONE)));
$icsContent[] = 'DTSTART;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventStart);
$icsContent[] = 'DTEND;TZID=' . self::TIMEZONE . ':' . $this->formatDateTimeLocal($eventEnd);
$icsContent[] = 'SUMMARY:' . $name;
$icsContent[] = 'LOCATION:' . $location;
$icsContent[] = 'END:VEVENT';
}
$icsContent[] = 'END:VCALENDAR';
return implode("\r\n", $icsContent);
}
private function isWeekend(array $day): bool
{
return $day['weekday'] >= 6;
}
private function formatDateTime(DateTimeInterface $dateTime): string
{
$utcDateTime = clone $dateTime;
$utcDateTime->setTimezone(new DateTimeZone('UTC'));
return $utcDateTime->format('Ymd\THis\Z');
}
private function formatDateTimeLocal(DateTimeInterface $dateTime): string
{
return $dateTime->format('Ymd\THis');
}
private function generateUid(DateTimeInterface $dateTime): string
{
return $dateTime->format('Ymd') . '-' . md5($dateTime->format('Ymd') . '-CHECK24') . '@check24.de';
}
private function generateTimezone(): string
{
return implode("\r\n", [
'BEGIN:VTIMEZONE',
'TZID:' . self::TIMEZONE,
'X-LIC-LOCATION:' . self::TIMEZONE,
'BEGIN:DAYLIGHT',
'TZOFFSETFROM:+0100',
'TZOFFSETTO:+0200',
'TZNAME:CEST',
'DTSTART:19700329T020000',
'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU',
'END:DAYLIGHT',
'BEGIN:STANDARD',
'TZOFFSETFROM:+0200',
'TZOFFSETTO:+0100',
'TZNAME:CET',
'DTSTART:19701025T030000',
'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU',
'END:STANDARD',
'END:VTIMEZONE',
]);
}
}