mirror of
https://github.com/itflow-org/itflow
synced 2026-08-05 07:07:14 +00:00
Feature: Calendars are now exportable shareable so Third Party Calendar services can read them, also added recoccurence to calendar, and all day along with sperating the time fields and click in a box to add calendar event is possible
This commit is contained in:
114
guest/guest_calendar_feed.php
Normal file
114
guest/guest_calendar_feed.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* guest_calendar_feed.php
|
||||
* Read-only iCalendar (ICS) feed for a single ITFlow calendar
|
||||
*
|
||||
* Unauthenticated by design - Google Calendar and Nextcloud fetch subscription
|
||||
* URLs with no credentials at all, so possession of calendar_feed_key is the
|
||||
* only authorisation. The key is 32 URL-safe base64 characters (~192 bits).
|
||||
*
|
||||
* Deliberately does NOT include guest/includes/inc_all_guest.php (that emits the
|
||||
* guest HTML layout) and deliberately does NOT start a session - an unattended
|
||||
* fetcher hitting this every 15 minutes should not be creating session files.
|
||||
*/
|
||||
|
||||
require_once "../config.php";
|
||||
require_once "../functions.php";
|
||||
require_once "../includes/load_global_settings.php";
|
||||
require_once "../includes/inc_set_timezone.php";
|
||||
|
||||
// How much of the calendar to publish. Recurring events are always included
|
||||
// regardless of window, since their first occurrence can predate it.
|
||||
define("FEED_MONTHS_PAST", 12);
|
||||
define("FEED_MONTHS_FUTURE", 24);
|
||||
|
||||
// Never let a leaked feed URL end up in a search index
|
||||
header("X-Robots-Tag: noindex, nofollow");
|
||||
|
||||
/*
|
||||
* Bad or missing key - identical response either way, so the endpoint cannot be
|
||||
* used to tell a wrong key from a revoked one
|
||||
*/
|
||||
function feedNotFound() {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
header("Content-Type: text/plain; charset=utf-8");
|
||||
echo "Not found.";
|
||||
exit();
|
||||
}
|
||||
|
||||
if (!isset($_GET['key']) || strlen($_GET['key']) < 16 || strlen($_GET['key']) > 64) {
|
||||
feedNotFound();
|
||||
}
|
||||
|
||||
$feed_key = escapeSql($_GET['key']);
|
||||
|
||||
$sql = mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT * FROM calendars
|
||||
WHERE calendar_feed_key = '$feed_key'
|
||||
AND calendar_feed_key IS NOT NULL
|
||||
AND calendar_archived_at IS NULL
|
||||
LIMIT 1"
|
||||
);
|
||||
|
||||
if (mysqli_num_rows($sql) !== 1) {
|
||||
feedNotFound();
|
||||
}
|
||||
|
||||
$calendar = mysqli_fetch_assoc($sql);
|
||||
$calendar_id = intval($calendar['calendar_id']);
|
||||
|
||||
// Gather events for this calendar only
|
||||
$window_start = date('Y-m-d H:i:s', strtotime('-' . FEED_MONTHS_PAST . ' months'));
|
||||
$window_end = date('Y-m-d H:i:s', strtotime('+' . FEED_MONTHS_FUTURE . ' months'));
|
||||
|
||||
$events_sql = mysqli_query(
|
||||
$mysqli,
|
||||
"SELECT * FROM calendar_events
|
||||
WHERE event_calendar_id = $calendar_id
|
||||
AND event_archived_at IS NULL
|
||||
AND (
|
||||
(event_repeat IS NOT NULL AND event_repeat != '')
|
||||
OR event_start BETWEEN '$window_start' AND '$window_end'
|
||||
)
|
||||
ORDER BY event_start ASC"
|
||||
);
|
||||
|
||||
$events = [];
|
||||
while ($row = mysqli_fetch_assoc($events_sql)) {
|
||||
$events[] = $row;
|
||||
}
|
||||
|
||||
$ics = buildCalendarFeedIcs($calendar, $events, $config_timezone, $config_base_url);
|
||||
|
||||
// Let clients that send If-None-Match skip the transfer entirely
|
||||
$etag = '"' . md5($ics) . '"';
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim($_SERVER['HTTP_IF_NONE_MATCH']) === $etag) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
header("ETag: $etag");
|
||||
exit();
|
||||
}
|
||||
|
||||
// Record that something is actually subscribed, without turning a hammered URL
|
||||
// into one write per request
|
||||
mysqli_query(
|
||||
$mysqli,
|
||||
"UPDATE calendars
|
||||
SET calendar_feed_accessed_at = NOW()
|
||||
WHERE calendar_id = $calendar_id
|
||||
AND (calendar_feed_accessed_at IS NULL OR calendar_feed_accessed_at < DATE_SUB(NOW(), INTERVAL 5 MINUTE))"
|
||||
);
|
||||
|
||||
$filename = preg_replace('/[^A-Za-z0-9_-]/', '_', $calendar['calendar_name']) . '.ics';
|
||||
|
||||
header("Content-Type: text/calendar; charset=utf-8");
|
||||
header("Content-Disposition: inline; filename=\"$filename\"");
|
||||
// No explicit Content-Length - mod_deflate or nginx gzip would compress the body
|
||||
// after this point and leave the declared length wrong, which some clients treat
|
||||
// as a truncated response
|
||||
header("Cache-Control: private, max-age=300");
|
||||
header("ETag: $etag");
|
||||
|
||||
echo $ics;
|
||||
Reference in New Issue
Block a user