diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d52ba9c..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -include/config.php -cache/*.ics \ No newline at end of file diff --git a/include/ICal/Event.php b/ICal/Event.php similarity index 99% rename from include/ICal/Event.php rename to ICal/Event.php index 354c7be..8845a66 100644 --- a/include/ICal/Event.php +++ b/ICal/Event.php @@ -261,4 +261,4 @@ class Event return strtolower($inputSplit); } -} \ No newline at end of file +} diff --git a/include/ICal/ICal.php b/ICal/ICal.php similarity index 97% rename from include/ICal/ICal.php rename to ICal/ICal.php index 179923e..7098c3b 100644 --- a/include/ICal/ICal.php +++ b/ICal/ICal.php @@ -92,18 +92,16 @@ class ICal public $disableCharacterReplacement = false; /** - * 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. + * With this being non-null the parser will ignore all events more than roughly this many days after now. * - * @var \DateTimeInterface|integer|null + * @var integer|null */ public $filterDaysBefore; /** - * 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. + * With this being non-null the parser will ignore all events more than roughly this many days before now. * - * @var \DateTimeInterface|integer|null + * @var integer|null */ public $filterDaysAfter; @@ -525,33 +523,8 @@ class ICal // Ideally you would use `PHP_INT_MIN` from PHP 7 $php_int_min = -2147483648; - $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->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->shouldFilterByWindow = !is_null($this->filterDaysBefore) || !is_null($this->filterDaysAfter); @@ -559,7 +532,7 @@ class ICal $files = is_array($files) ? $files : array($files); foreach ($files as $file) { - if (is_string($file) && $this->isFileOrUrl($file)) { + if (!is_array($file) && $this->isFileOrUrl($file)) { $lines = $this->fileOrUrl($file); } else { $lines = is_array($file) ? $file : array($file); @@ -1171,9 +1144,9 @@ class ICal */ $pattern = '/^(?:TZID=)?([^:]*|".*")'; // [1]: Time zone $pattern .= ':?'; // Time zone delimiter - $pattern .= '(\d{8})'; // [2]: YYYYMMDD + $pattern .= '([0-9]{8})'; // [2]: YYYYMMDD $pattern .= 'T?'; // Time delimiter - $pattern .= '(?(?<=T)(\d{6}))'; // [3]: HHMMSS (filled if delimiter present) + $pattern .= '(?(?<=T)([0-9]{6}))'; // [3]: HHMMSS (filled if delimiter present) $pattern .= '(Z?)/'; // [4]: UTC flag preg_match($pattern, $icalDate, $date); @@ -1356,7 +1329,7 @@ class ICal } $allEventRecurrences = array(); - $eventKeysToRemove = array(); + $eventKeysToRemove = array(); foreach ($events as $key => $anEvent) { if (!isset($anEvent['RRULE']) || $anEvent['RRULE'] === '') { @@ -1380,7 +1353,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] = $v === '' ? array() : explode(',', $v); + $rrules[$k] = explode(',', $v); } else { $rrules[$k] = $v; } @@ -1422,7 +1395,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)) { // @phpstan-ignore function.alreadyNarrowedType + if (!is_int($this->defaultSpan)) { trigger_error('ICal::defaultSpan: User defined value is not an integer', E_USER_NOTICE); } @@ -1594,7 +1567,7 @@ class ICal $clonedDateTime = clone $frequencyRecurringDateTime; $candidateDateTimes[] = $clonedDateTime->setDate( (int) $frequencyRecurringDateTime->format('Y'), - (int) $frequencyRecurringDateTime->format('n'), + (int) $frequencyRecurringDateTime->format('m'), $day ); } @@ -1627,7 +1600,7 @@ class ICal foreach ($monthDays as $day) { $matchingDays[] = $bymonthRecurringDatetime->setDate( (int) $frequencyRecurringDateTime->format('Y'), - (int) $bymonthRecurringDatetime->format('n'), + (int) $bymonthRecurringDatetime->format('m'), $day )->format('z') + 1; } @@ -1709,7 +1682,7 @@ class ICal } // Move forwards $interval $frequency. - $monthPreMove = (int) $frequencyRecurringDateTime->format('n'); + $monthPreMove = $frequencyRecurringDateTime->format('m'); $frequencyRecurringDateTime->modify("{$interval} {$this->frequencyConversion[$frequency]}"); // As noted in Example #2 on https://www.php.net/manual/en/datetime.modify.php, @@ -1717,7 +1690,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 = (int) $frequencyRecurringDateTime->format('n') - $monthPreMove; + $monthDiff = $frequencyRecurringDateTime->format('m') - $monthPreMove; if (($monthDiff > 0 && $monthDiff > $interval) || ($monthDiff < 0 && $monthDiff > $interval - 12)) { $frequencyRecurringDateTime->modify('-1 month'); @@ -1727,7 +1700,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) && (int) $frequencyRecurringDateTime->format('n') !== $monthPreMove) { + if (isset($monthDays) && $frequencyRecurringDateTime->format('m') !== $monthPreMove) { unset($monthDays); } } @@ -2077,7 +2050,7 @@ class ICal foreach ($monthDays as $day) { $matchingDays[] = $monthDateTime->setDate( (int) $initialDateTime->format('Y'), - (int) $monthDateTime->format('n'), + (int) $monthDateTime->format('m'), $day )->format('z') + 1; } @@ -2417,9 +2390,7 @@ class ICal foreach ($tza as $zone) { foreach ($zone as $item) { - if ($item['timezone_id'] !== null) { - $valid[$item['timezone_id']] = true; - } + $valid[$item['timezone_id']] = true; } } @@ -2489,7 +2460,7 @@ class ICal */ protected function removeUnprintableChars($data) { - return preg_replace('/[\x00-\x1F\x7F]/u', '', $data); + return preg_replace('/[\x00-\x1F\x7F\xA0]/u', '', $data); } /** @@ -2503,19 +2474,19 @@ class ICal { if (function_exists('mb_chr')) { return mb_chr($code); - } - - if (($code %= 0x200000) < 0x80) { - $s = chr($code); - } elseif ($code < 0x800) { - $s = chr(0xc0 | $code >> 6) . chr(0x80 | $code & 0x3f); - } elseif ($code < 0x10000) { - $s = chr(0xe0 | $code >> 12) . chr(0x80 | $code >> 6 & 0x3f) . chr(0x80 | $code & 0x3f); } else { - $s = chr(0xf0 | $code >> 18) . chr(0x80 | $code >> 12 & 0x3f) . chr(0x80 | $code >> 6 & 0x3f) . chr(0x80 | $code & 0x3f); - } + if (($code %= 0x200000) < 0x80) { + $s = chr($code); + } elseif ($code < 0x800) { + $s = chr(0xc0 | $code >> 6) . chr(0x80 | $code & 0x3f); + } elseif ($code < 0x10000) { + $s = chr(0xe0 | $code >> 12) . chr(0x80 | $code >> 6 & 0x3f) . chr(0x80 | $code & 0x3f); + } else { + $s = chr(0xf0 | $code >> 18) . chr(0x80 | $code >> 12 & 0x3f) . chr(0x80 | $code >> 6 & 0x3f) . chr(0x80 | $code & 0x3f); + } - return $s; + return $s; + } } /** @@ -2579,10 +2550,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()); @@ -2618,6 +2589,7 @@ class ICal * * @param string $value * @return boolean + * @throws \Exception */ public function isValidDate($value) { @@ -2658,7 +2630,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']; @@ -2729,4 +2701,4 @@ class ICal return new \DateTimeZone($this->getDefaultTimeZone()); } -} \ No newline at end of file +} diff --git a/ICal/LICENSE b/ICal/LICENSE new file mode 100644 index 0000000..0708674 --- /dev/null +++ b/ICal/LICENSE @@ -0,0 +1,15 @@ +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. diff --git a/cache/index.php b/cache/index.php deleted file mode 100644 index 312a51d..0000000 --- a/cache/index.php +++ /dev/null @@ -1,53 +0,0 @@ - ['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); - } -} - -if (file_exists($cachefile)) { - $content = file_get_contents($cachefile); - - // Replace X-WR-CALNAME - $content = preg_replace('/X-WR-CALNAME:.*(\r\n|\n|\r)/i', "X-WR-CALNAME:" . $ical_name . "\r\n", $content); - - // Remove all ORGANIZER - $content = preg_replace('/^ORGANIZER[:;].*(?:\r?\n[\t ].*)*(\r?\n)?/mi', '', $content); - - // Filter CATEGORIES - $exclude = $_GET['exclude'] ?? ''; - - if ($exclude && strlen($exclude) < 32 && preg_match('/^[a-zA-Z0-9_|-]+$/', $exclude)) { - - $parts = explode('|', $exclude); - $safe_parts = array_map(function($part) { - return preg_quote(trim($part), '/'); - }, $parts); - $safe_exclude = implode('|', $safe_parts); - - $filtered_content = preg_replace('/BEGIN:VEVENT(?!(?:(?!END:VEVENT)[\s\S])*CATEGORIES:(?:(?!' . $safe_exclude . ')[\s\S])*?(?:\r?\n|;))(?:(?!END:VEVENT)[\s\S])*END:VEVENT(?:\r?\n)?/ms', '', $content); - - if ($filtered_content !== null) { - $content = $filtered_content; - } - } - - header('Content-Type: text/calendar; charset=utf-8'); - header('Content-Disposition: attachment; filename="' . $ical_file . '"'); - header('Content-Description: File Transfer'); - header('Cache-Control: public, max-age=120, must-revalidate'); - header('Content-Length: ' . strlen($content)); - - echo $content; - exit; -} else { - die("File not found."); -} \ No newline at end of file diff --git a/include/config.sample.php b/include/config.sample.php deleted file mode 100644 index a6c5f49..0000000 --- a/include/config.sample.php +++ /dev/null @@ -1,11 +0,0 @@ - ['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); - } else { - if (file_exists($cachefile)) $ical->initFile($cachefile); - } +if(@filemtime($cachefile) + $cachetime < time()) { + $ical_str = file_get_contents($ical); + file_put_contents($cachefile, $ical_str); + $iCal->initString($ical_str); } else { - $ical->initFile($cachefile); + $iCal->initFile($cachefile); } +//$iCal->initURL($ical); # Load calendar entries -$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 +$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); -$events = $ical->sortEventsWithOrder($ical->eventsFromInterval($period_val . ' month')); $result = []; - foreach ($events as $event) { - $cat = $event->categories ?? ''; - if ($filter && stripos($cat, $filter) === false) continue; - if (stripos($cat, "hidden") !== false) continue; + if ($filter && strpos($event->categories, $filter) === false) { + continue; + } - $start = new DateTime($event->dtstart); - $end = new DateTime($event->dtend); - $uid = $event->uid; + if (isset($event->categories) && strpos($event->categories, "hidden")) { + continue; + } - $interval = new DateInterval('P1D'); - $period = new DatePeriod($start, $interval, $end); + $start = new DateTime($event->dtstart); + $end = new DateTime($event->dtend); + $uid = $event->uid; + + $interval = DateInterval::createFromDateString('1 day'); + $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; + } - foreach ($period as $dt) { - $date = $dt->format("Y-m-d"); - $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; charset=utf-8"); -echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); \ No newline at end of file +header("Content-Type: application/json"); + +echo json_encode($result, JSON_UNESCAPED_SLASHES); \ No newline at end of file