kithkin/app/Http/Controllers/EventController.php

244 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use App\Models\Calendar;
use App\Models\Event;
use App\Models\EventMeta;
class EventController extends Controller
{
/**
* create a new event page
*/
public function create(Calendar $calendar)
{
$this->authorize('update', $calendar);
$instance = $calendar->instanceForUser();
$event = new Event;
$event->meta = (object) [
'title' => '',
'description' => '',
'location' => '',
'start_at' => null,
'end_at' => null,
'all_day' => false,
'category' => '',
];
$start = $event->start_at;
$end = $event->end_at;
return view('event.form', compact('calendar', 'instance', 'event', 'start', 'end'));
}
/**
* edit event page
*/
public function edit(Calendar $calendar, Event $event)
{
$this->authorize('update', $calendar);
$instance = $calendar->instanceForUser();
$timezone = $instance?->timezone ?? 'UTC';
$start = optional($event->meta?->start_at)
?->timezone($timezone)
?->format('Y-m-d\TH:i');
$end = optional($event->meta?->end_at)
?->timezone($timezone)
?->format('Y-m-d\TH:i');
return view('event.form', compact('calendar', 'instance', 'event', 'start', 'end'));
}
/**
* single event view handling
*
* URL: /calendar/{uuid}/event/{event_id}
*/
public function show(Request $request, Calendar $calendar, Event $event)
{
// ensure the event really belongs to the parent calendar
if ((int) $event->calendarid !== (int) $calendar->id) {
abort(Response::HTTP_NOT_FOUND);
}
// authorize
$this->authorize('view', $event);
// eager-load meta so the view has everything
$event->load('meta');
// check for HTML; it sends `HX-Request: true` on every AJAX call
$isHtmx = $request->header('HX-Request') === 'true';
// convert Sabre timestamps if meta is missing
$start = $event->meta->start_at
?? Carbon::createFromTimestamp($event->firstoccurence);
$end = $event->meta->end_at
?? ($event->lastoccurence
? Carbon::createFromTimestamp($event->lastoccurence)
: $start);
$data = compact('calendar', 'event', 'start', 'end');
return $isHtmx
? view('event.partials.details', $data) // tiny fragment for the modal
: view('event.show', $data); // full-page fallback
}
/**
* BACKEND METHODS
*
*/
/**
* insert vevent into sabres calendarobjects + meta row
*/
public function store(Request $req, Calendar $calendar)
{
$this->authorize('update', $calendar);
$data = $req->validate([
'title' => 'required|string|max:200',
'start_at' => 'required|date',
'end_at' => 'required|date|after:start_at',
'description' => 'nullable|string',
'location' => 'nullable|string',
'all_day' => 'sometimes|boolean',
'category' => 'nullable|string|max:50',
]);
// prepare payload
$uid = Str::uuid() . '@' . parse_url(config('app.url'), PHP_URL_HOST);
// store events as UTC in the database; convert to calendar time in the view
$client_timezone = $calendar->timezone ?? 'UTC';
$start = Carbon::createFromFormat('Y-m-d\TH:i', $data['start_at'], $client_timezone)
->setTimezone('UTC');
$end = Carbon::createFromFormat('Y-m-d\TH:i', $data['end_at'], $client_timezone)
->setTimezone('UTC');
// prepare strings
$description = $data['description'] ?? '';
$location = $data['location'] ?? '';
$description = str_replace("\n", '\\n', $description);
$location = str_replace("\n", '\\n', $location);
/* build minimal iCalendar payload */
$ical = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Kithkin//Laravel CalDAV//EN
BEGIN:VEVENT
UID:$uid
DTSTAMP:{$start->utc()->format('Ymd\\THis\\Z')}
DTSTART:{$start->format('Ymd\\THis')}
DTEND:{$end->format('Ymd\\THis')}
SUMMARY:{$data['title']}
DESCRIPTION:$description
LOCATION:$location
END:VEVENT
END:VCALENDAR
ICS;
$event = Event::create([
'calendarid' => $calendar->id,
'uri' => Str::uuid() . '.ics',
'lastmodified' => time(),
'etag' => md5($ical),
'size' => strlen($ical),
'componenttype' => 'VEVENT',
'uid' => $uid,
'calendardata' => $ical,
]);
$event->meta()->create([
'title' => $data['title'],
'description' => $data['description'] ?? null,
'location' => $data['location'] ?? null,
'all_day' => $data['all_day'] ?? false,
'category' => $data['category'] ?? null,
'start_at' => $start,
'end_at' => $end,
]);
return redirect()->route('calendar.show', $calendar);
}
/**
* update vevent + meta
*/
public function update(Request $req, Calendar $calendar, Event $event)
{
$this->authorize('update', $calendar);
$data = $req->validate([
'title' => 'required|string|max:200',
'start_at' => 'required|date',
'end_at' => 'required|date|after:start_at',
'description' => 'nullable|string',
'location' => 'nullable|string',
'category' => 'nullable|string|max:50',
]);
// rebuild the icalendar payload
$calendar_timezone = $calendar->timezone ?? 'UTC';
$start = Carbon::createFromFormat('Y-m-d\TH:i', $data['start_at'], $calendar_timezone)->setTimezone($calendar_timezone);
$end = Carbon::createFromFormat('Y-m-d\TH:i', $data['end_at'], $calendar_timezone)->setTimezone($calendar_timezone);
// prepare strings
$description = $data['description'] ?? '';
$location = $data['location'] ?? '';
$description = str_replace("\n", '\\n', $description);
$location = str_replace("\n", '\\n', $location);
// note: keep the UID stable (CalDAV relies on it!)
$uid = $event->uid;
$ical = <<<ICS
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Kithkin//Laravel CalDAV//EN
BEGIN:VEVENT
UID:$uid
DTSTAMP:{$start->utc()->format('Ymd\\THis\\Z')}
DTSTART;TZID={$calendar_timezone}:{$start->format('Ymd\\THis')}
DTEND;TZID={$calendar_timezone}:{$end->format('Ymd\\THis')}
SUMMARY:{$data['title']}
DESCRIPTION:$description
LOCATION:$location
END:VEVENT
END:VCALENDAR
ICS;
// persist changes
$event->update([
'calendardata' => $ical,
'etag' => md5($ical),
'lastmodified' => time(),
]);
$event->meta()->updateOrCreate([], [
'title' => $data['title'],
'description' => $data['description'] ?? null,
'location' => $data['location'] ?? null,
'all_day' => $data['all_day'] ?? false,
'category' => $data['category'] ?? null,
'start_at' => $start,
'end_at' => $end,
]);
return redirect()->route('calendar.show', $calendar);
}
}