diff --git a/composer.lock b/composer.lock index ac24c18..d5b99f5 100644 --- a/composer.lock +++ b/composer.lock @@ -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", diff --git a/src/Controller/CalendarController.php b/src/Controller/CalendarController.php index 4d90e7e..fb903ee 100644 --- a/src/Controller/CalendarController.php +++ b/src/Controller/CalendarController.php @@ -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; + } } \ No newline at end of file diff --git a/src/Service/CalendarExportService.php b/src/Service/CalendarExportService.php new file mode 100644 index 0000000..96a1cea --- /dev/null +++ b/src/Service/CalendarExportService.php @@ -0,0 +1,107 @@ +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', + ]); + } +} \ No newline at end of file