Move ICal module to include folder, export config to separate config file

This commit is contained in:
Patrick Schwarz 2026-01-20 00:32:08 +01:00
parent e1fe24e6af
commit 3fcbd8e16f
5 changed files with 121 additions and 103 deletions

View file

@ -1,15 +0,0 @@
The MIT License (MIT)
Copyright (c) 2018
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -92,16 +92,18 @@ class ICal
public $disableCharacterReplacement = false;
/**
* With this being non-null the parser will ignore all events more than roughly this many days after now.
* If this value is an integer, the parser will ignore all events more than roughly this many days before now.
* If this value is a date, the parser will ignore all events occurring before this date.
*
* @var integer|null
* @var \DateTimeInterface|integer|null
*/
public $filterDaysBefore;
/**
* With this being non-null the parser will ignore all events more than roughly this many days before now.
* If this value is an integer, the parser will ignore all events more than roughly this many days after now.
* If this value is a date, the parser will ignore all events occurring after this date.
*
* @var integer|null
* @var \DateTimeInterface|integer|null
*/
public $filterDaysAfter;
@ -523,8 +525,33 @@ class ICal
// Ideally you would use `PHP_INT_MIN` from PHP 7
$php_int_min = -2147483648;
$this->windowMinTimestamp = is_null($this->filterDaysBefore) ? $php_int_min : (new \DateTime('now'))->sub(new \DateInterval('P' . $this->filterDaysBefore . 'D'))->getTimestamp();
$this->windowMaxTimestamp = is_null($this->filterDaysAfter) ? PHP_INT_MAX : (new \DateTime('now'))->add(new \DateInterval('P' . $this->filterDaysAfter . 'D'))->getTimestamp();
$this->windowMinTimestamp = $php_int_min;
if (!is_null($this->filterDaysBefore)) {
if (is_int($this->filterDaysBefore)) {
$this->windowMinTimestamp = (new \DateTime('now'))
->sub(new \DateInterval('P' . $this->filterDaysBefore . 'D'))
->getTimestamp();
}
if ($this->filterDaysBefore instanceof \DateTimeInterface) {
$this->windowMinTimestamp = $this->filterDaysBefore->getTimestamp();
}
}
$this->windowMaxTimestamp = PHP_INT_MAX;
if (!is_null($this->filterDaysAfter)) {
if (is_int($this->filterDaysAfter)) {
$this->windowMaxTimestamp = (new \DateTime('now'))
->add(new \DateInterval('P' . $this->filterDaysAfter . 'D'))
->getTimestamp();
}
if ($this->filterDaysAfter instanceof \DateTimeInterface) {
$this->windowMaxTimestamp = $this->filterDaysAfter->getTimestamp();
}
}
$this->shouldFilterByWindow = !is_null($this->filterDaysBefore) || !is_null($this->filterDaysAfter);
@ -532,7 +559,7 @@ class ICal
$files = is_array($files) ? $files : array($files);
foreach ($files as $file) {
if (!is_array($file) && $this->isFileOrUrl($file)) {
if (is_string($file) && $this->isFileOrUrl($file)) {
$lines = $this->fileOrUrl($file);
} else {
$lines = is_array($file) ? $file : array($file);
@ -1144,9 +1171,9 @@ class ICal
*/
$pattern = '/^(?:TZID=)?([^:]*|".*")'; // [1]: Time zone
$pattern .= ':?'; // Time zone delimiter
$pattern .= '([0-9]{8})'; // [2]: YYYYMMDD
$pattern .= '(\d{8})'; // [2]: YYYYMMDD
$pattern .= 'T?'; // Time delimiter
$pattern .= '(?(?<=T)([0-9]{6}))'; // [3]: HHMMSS (filled if delimiter present)
$pattern .= '(?(?<=T)(\d{6}))'; // [3]: HHMMSS (filled if delimiter present)
$pattern .= '(Z?)/'; // [4]: UTC flag
preg_match($pattern, $icalDate, $date);
@ -1353,7 +1380,7 @@ class ICal
foreach (array_filter(explode(';', $anEvent['RRULE'])) as $s) {
list($k, $v) = explode('=', $s);
if (in_array($k, array('BYSETPOS', 'BYDAY', 'BYMONTHDAY', 'BYMONTH', 'BYYEARDAY', 'BYWEEKNO'))) {
$rrules[$k] = explode(',', $v);
$rrules[$k] = $v === '' ? array() : explode(',', $v);
} else {
$rrules[$k] = $v;
}
@ -1395,7 +1422,7 @@ class ICal
$interval = (empty($rrules['INTERVAL'])) ? 1 : (int) $rrules['INTERVAL'];
// Throw an error if this isn't an integer.
if (!is_int($this->defaultSpan)) {
if (!is_int($this->defaultSpan)) { // @phpstan-ignore function.alreadyNarrowedType
trigger_error('ICal::defaultSpan: User defined value is not an integer', E_USER_NOTICE);
}
@ -1567,7 +1594,7 @@ class ICal
$clonedDateTime = clone $frequencyRecurringDateTime;
$candidateDateTimes[] = $clonedDateTime->setDate(
(int) $frequencyRecurringDateTime->format('Y'),
(int) $frequencyRecurringDateTime->format('m'),
(int) $frequencyRecurringDateTime->format('n'),
$day
);
}
@ -1600,7 +1627,7 @@ class ICal
foreach ($monthDays as $day) {
$matchingDays[] = $bymonthRecurringDatetime->setDate(
(int) $frequencyRecurringDateTime->format('Y'),
(int) $bymonthRecurringDatetime->format('m'),
(int) $bymonthRecurringDatetime->format('n'),
$day
)->format('z') + 1;
}
@ -1682,7 +1709,7 @@ class ICal
}
// Move forwards $interval $frequency.
$monthPreMove = $frequencyRecurringDateTime->format('m');
$monthPreMove = (int) $frequencyRecurringDateTime->format('n');
$frequencyRecurringDateTime->modify("{$interval} {$this->frequencyConversion[$frequency]}");
// As noted in Example #2 on https://www.php.net/manual/en/datetime.modify.php,
@ -1690,7 +1717,7 @@ class ICal
// expect. For instance: January 31st + 1 month == March 3rd (March 2nd on a leap
// year.) The following code crudely rectifies this.
if ($frequency === 'MONTHLY') {
$monthDiff = $frequencyRecurringDateTime->format('m') - $monthPreMove;
$monthDiff = (int) $frequencyRecurringDateTime->format('n') - $monthPreMove;
if (($monthDiff > 0 && $monthDiff > $interval) || ($monthDiff < 0 && $monthDiff > $interval - 12)) {
$frequencyRecurringDateTime->modify('-1 month');
@ -1700,7 +1727,7 @@ class ICal
// $monthDays is set in the DAILY frequency if the BYMONTHDAY stanza is present in
// the RRULE. The variable only needs to be updated when we change months, so we
// unset it here, prompting a recreation next iteration.
if (isset($monthDays) && $frequencyRecurringDateTime->format('m') !== $monthPreMove) {
if (isset($monthDays) && (int) $frequencyRecurringDateTime->format('n') !== $monthPreMove) {
unset($monthDays);
}
}
@ -2050,7 +2077,7 @@ class ICal
foreach ($monthDays as $day) {
$matchingDays[] = $monthDateTime->setDate(
(int) $initialDateTime->format('Y'),
(int) $monthDateTime->format('m'),
(int) $monthDateTime->format('n'),
$day
)->format('z') + 1;
}
@ -2390,9 +2417,11 @@ class ICal
foreach ($tza as $zone) {
foreach ($zone as $item) {
if ($item['timezone_id'] !== null) {
$valid[$item['timezone_id']] = true;
}
}
}
unset($valid['']);
@ -2460,7 +2489,7 @@ class ICal
*/
protected function removeUnprintableChars($data)
{
return preg_replace('/[\x00-\x1F\x7F\xA0]/u', '', $data);
return preg_replace('/[\x00-\x1F\x7F]/u', '', $data);
}
/**
@ -2474,7 +2503,8 @@ class ICal
{
if (function_exists('mb_chr')) {
return mb_chr($code);
} else {
}
if (($code %= 0x200000) < 0x80) {
$s = chr($code);
} elseif ($code < 0x800) {
@ -2487,7 +2517,6 @@ class ICal
return $s;
}
}
/**
* Places double-quotes around texts that have characters not permitted
@ -2550,10 +2579,10 @@ class ICal
{
if (empty($event['EXDATE_array'])) {
return array();
} else {
$exdates = $event['EXDATE_array'];
}
$exdates = $event['EXDATE_array'];
$output = array();
$currentTimeZone = new \DateTimeZone($this->getDefaultTimeZone());
@ -2589,7 +2618,6 @@ class ICal
*
* @param string $value
* @return boolean
* @throws \Exception
*/
public function isValidDate($value)
{
@ -2630,7 +2658,7 @@ class ICal
$options['http'] = array();
$options['http']['header'] = array();
if ($this->httpBasicAuth === array() || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) {
if ($this->httpBasicAuth !== array() || !empty($this->httpUserAgent) || !empty($this->httpAcceptLanguage)) {
if ($this->httpBasicAuth !== array()) {
$username = $this->httpBasicAuth['username'];
$password = $this->httpBasicAuth['password'];

11
include/config.sample.php Normal file
View file

@ -0,0 +1,11 @@
<?php
#ini_set('display_errors', 1);
#ini_set('display_startup_errors', 1);
#error_reporting(E_ALL);
$ical_url = '...';
$ical_name = 'My Calendar File';
$cachefile = 'calendar.ics';
$cachetime = 120; // 2 min

View file

@ -1,72 +1,66 @@
<?php
#ini_set('display_errors', 1);
#ini_set('display_startup_errors', 1);
#error_reporting(E_ALL);
# config
$ical = 'https://cloud.hacknang.de/remote.php/dav/public-calendars/nZTMSHpd29ZRpAr6/?export';
$cachefile = 'cache/hacknang.ics';
$cachetime = 120; // 2 min
# requirements
require_once 'ICal/ICal.php';
require_once 'ICal/Event.php';
require_once 'include/config.php';
require_once 'include/ICal/ICal.php';
require_once 'include/ICal/Event.php';
# Fix relative path
$cachefile = 'cache/'.$cachefile;
# Init Calendar
$iCal = new \ICal\ICal();
$ical = new \ICal\ICal();
# Caching and load calendar
if(@filemtime($cachefile) + $cachetime < time()) {
$ical_str = file_get_contents($ical);
if (!file_exists($cachefile) || (filemtime($cachefile) + $cachetime < time())) {
$context = stream_context_create(['http' => ['timeout' => 10]]);
$ical_str = @file_get_contents($ical_url, false, $context);
if ($ical_str !== false && strpos($ical_str, 'BEGIN:VCALENDAR') !== false) {
file_put_contents($cachefile, $ical_str);
$iCal->initString($ical_str);
$ical->initString($ical_str);
} else {
$iCal->initFile($cachefile);
if (file_exists($cachefile)) $ical->initFile($cachefile);
}
} else {
$ical->initFile($cachefile);
}
//$iCal->initURL($ical);
# Load calendar entries
$months = max(filter_input(INPUT_GET, 'period', FILTER_VALIDATE_INT, array('options' => array('min_range' => 1, 'max_range' => 12))), 1);
$events = $iCal->sortEventsWithOrder($iCal->eventsFromInterval($months.' month'));
$filter = filter_input(INPUT_GET, 'filter', FILTER_SANITIZE_SPECIAL_CHARS);
$period_val = filter_input(INPUT_GET, 'period', FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 12]]) ?: 1;
$filter = filter_input(INPUT_GET, 'filter', FILTER_SANITIZE_SPECIAL_CHARS); // FILTER_UNSAFE_RAW
$events = $ical->sortEventsWithOrder($ical->eventsFromInterval($period_val . ' month'));
$result = [];
foreach ($events as $event) {
$cat = $event->categories ?? '';
if ($filter && strpos($event->categories, $filter) === false) {
continue;
}
if (isset($event->categories) && strpos($event->categories, "hidden")) {
continue;
}
if ($filter && stripos($cat, $filter) === false) continue;
if (stripos($cat, "hidden") !== false) continue;
$start = new DateTime($event->dtstart);
$end = new DateTime($event->dtend);
$uid = $event->uid;
$interval = DateInterval::createFromDateString('1 day');
$interval = new DateInterval('P1D');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt) {
$date = $dt->format("Y-m-d");
$result[$date][$uid]["dtstart"] = $iCal->iCalDateToDateTime($event->dtstart_array[3])->format(DateTime::ATOM);
$result[$date][$uid]["dtend"] = $iCal->iCalDateToDateTime($event->dtend_array[3])->format(DateTime::ATOM);
$result[$date][$uid]["datestr"] = (isset($event->dtstart_array[0]["VALUE"]) && $event->dtstart_array[0]["VALUE"] == 'DATE')?'':$start->format('H:i');
$result[$date][$uid]["summary"] = mb_strimwidth($event->summary, 0, 255, "...");
$result[$date][$uid]["location"] = mb_strimwidth($event->location, 0, 255, "...");
$result[$date][$uid]["description"] = mb_strimwidth($event->description, 0, 255, "...");
if(isset($event->categories)) $result[$date][$uid]["categories"] = $event->categories;
$result[$date][$uid] = [
"dtstart" => $start->format(DateTime::ATOM), //$ical->iCalDateToDateTime($event->dtstart_array[3])->format(DateTime::ATOM);
"dtend" => $end->format(DateTime::ATOM), //$ical->iCalDateToDateTime($event->dtend_array[3])->format(DateTime::ATOM);
"datestr" => (isset($event->dtstart_array[0]["VALUE"]) && $event->dtstart_array[0]["VALUE"] == 'DATE') ? '' : $start->format('H:i'),
"summary" => mb_strimwidth($event->summary, 0, 255, "..."),
"location" => mb_strimwidth($event->location ?? '', 0, 255, "..."),
"description" => mb_strimwidth($event->description ?? '', 0, 255, "...")
];
if(!empty($cat)) $result[$date][$uid]["categories"] = $cat;
}
}
# Allow every page to load this json
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
echo json_encode($result, JSON_UNESCAPED_SLASHES);
header("Content-Type: application/json; charset=utf-8");
echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);