<?php
namespace App\Controller;
use App\Entity\Booking;
use App\Entity\Property;
use App\Entity\User;
use App\Entity\User\UserRole;
use App\Repository\BookingRepository;
use App\Repository\PropertyRepository;
use DateTime;
use DateTimeZone;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use ICal\ICal;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class IcsController extends AbstractController {
public function __construct(
private PropertyRepository $propertyRepository,
private BookingRepository $bookingRepository
) {}
/**
* @Route("/properties/{id}/ics/parsed", name="parseCalendar",
* requirements={"id"="[\w\-\+\.]+"}
* )
*/
public function parseCalendar(string $id, HttpClientInterface $httpClientInterface): Response {
throw new \Exception("Jayyrr");
return new JsonResponse([
'Fail'
], 404);
}
private static function ww(string $w): string {
return wordwrap($w, 65, "\n ", false);
}
protected static function vevent(Booking $b, string $desc, string $summary) {
$start = $b->getCheckin();
$end = $b->getCheckout();
$md5 = md5($b->getCalIdentifier());
// $desc = '$prop->getUncleanBookings("now")->count()} unclean units';
$summary = static::ww($summary);
$desc = static::ww($desc);
return <<<VEVENT
BEGIN:VEVENT
DTSTAMP;TZID=Europe/Oslo:{$start->format('Ymd')}T{$start->format('His')}
DTSTART;VALUE=DATE:{$start->format('Ymd')}
DTEND;VALUE=DATE:{$end->format('Ymd')}
UID:{$md5}@askepott.giaever.online
DESCRIPTION:{$desc}
SUMMARY:{$summary}
END:VEVENT
VEVENT;
}
protected static function vbegin(Property $property, array $rest): string {
$rest = join($rest);
return <<<VBEGIN
BEGIN:VCALENDAR
PRODID;X-RICAL-TZSOURCE=TZINFO:-//BNB.hosting//Simple Calendar 0.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
X-WR-CALNAME:{$property->getSection()}{$rest}
END:VCALENDAR
VBEGIN;
}
/**
* @Route({
* "/properties/{id}/ics/{action}",
* "/p/{id}/ics/{action}",
* "/ics/{id}/{action}",
* "/p/{id}/i/{action}"
* }, name="simpleCalendar",
* requirements={"id"="[\w\-\+\.]+"}
* )
*/
public function icsCalendar(string $id, string $action, Request $request) {
$property = $this->propertyRepository->find($id) ?: $this->propertyRepository->findOneBy([
'section' => strtoupper($id)
]) ?: null;
if ($property == null)
throw new NotFoundHttpException('Pollo');
$set = fn (string $key, mixed $default = null): bool => $request->get($key, $default) !== false;
$out = match($action) {
'unclean' => static::getUnclean($property),
default => static::getReservations($property, $set('only-bookings',
$set('bookings-only',
!$set('include-blocking', true)
)
))
};
if ($out->count() == 0)
$out->add(
static::vevent((new Booking($property))->setCheckin(
new \DateTimeImmutable("-365 days")
)->setCheckout(
new \DateTimeImmutable('-1 days')
), "Clean" , "Clean")
);
return new Response(str_replace("\n", "\r\n", static::vbegin($property, $out->map(
fn($a) => "\n" . $a
)->toArray())
), 200, [
'Content-type' => 'text/calendar; charset=utf-8',
'Content-Disposition' => sprintf('inline; filename=%s-%d-simple.ics', $property->getSection(), time())
]);
}
private static function getUnclean(Property $property): Collection {
return $property->getUncleanBookings()->map(
fn(Booking $b): string => static::vevent($b,
sprintf('Unclean from %s',
$b->getCheckout()->format('d-m-Y H:i')
), sprintf('Ready at %s',
$b->getProperty()->getTodaysCheckinTime($b->getCheckout())->format('d-m-Y H:i')
)
)
);
}
private static function getReservations(Property $property, bool $bookingsOnly = true): Collection|array {
return $property->getBookings(
Criteria::expr()->gte('checkout', new \DateTimeImmutable()),
Criteria::expr()->isNull('cancelledAt'),
...[Booking::cleanCriteria(false), ...($bookingsOnly ? [Criteria::expr()->neq('blocking', 1)] : [])]
)->map(
fn(Booking $b): string => match ($b->isBlocking()) {
true => static::vevent($b, sprintf('%s => %s',
$b->getCheckin()->format('d-m-Y H:i'),
$b->getCheckout()->format('d-m-Y H:i')
), sprintf('Blocked: %s', $b->getCalendarSource())),
default => static::vevent($b, sprintf('Check-in %s, Check-out: %s',
$b->getCheckin()->format('d-m-Y H:i'),
$b->getCheckout()->format('d-m-Y H:i')
), $b->getGuests() ? sprintf('%d guests', $b->getGuests() ?: 0) : 'Guests not set')
}
);
}
}