From 2048026c27e00aa08b1399813875804e854e2b3c Mon Sep 17 00:00:00 2001 From: +++ Date: Fri, 18 Feb 2022 11:59:12 +0100 Subject: [PATCH] =?UTF-8?q?neue=20ordnerstruktur,=20php-code=20hinzugef?= =?UTF-8?q?=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit die ordner public und src hinzugefügt, zapcallib.php für iCalendar files hinzugefügt, getContent.php und getEvent.php hinzugefügt --- favicon.ico => public/favicon.ico | Bin {images => public/images}/background.jpg | Bin {images => public/images/icons}/arrow-up.svg | 0 public/images/icons/bars.svg | 2 + .../images/icons}/user-times.svg | 0 public/images/icons/users.svg | 2 + {images => public/images}/lageplan.svg | 0 index.html => public/index.html | 4 +- style.css => public/style.css | 0 src/getContent.php | 102 ++ src/getEvents.php | 487 +++++++++ src/lib/README.md | 127 +++ src/lib/includes/date.php | 568 ++++++++++ src/lib/includes/framework.php | 32 + src/lib/includes/ical.php | 986 ++++++++++++++++++ src/lib/includes/index.html | 6 + src/lib/includes/recurringdate.php | 796 ++++++++++++++ src/lib/includes/timezone.php | 142 +++ src/lib/zapcallib.php | 28 + 19 files changed, 3281 insertions(+), 1 deletion(-) rename favicon.ico => public/favicon.ico (100%) rename {images => public/images}/background.jpg (100%) rename {images => public/images/icons}/arrow-up.svg (100%) create mode 100644 public/images/icons/bars.svg rename {images => public/images/icons}/user-times.svg (100%) create mode 100644 public/images/icons/users.svg rename {images => public/images}/lageplan.svg (100%) rename index.html => public/index.html (98%) rename style.css => public/style.css (100%) create mode 100644 src/getContent.php create mode 100644 src/getEvents.php create mode 100644 src/lib/README.md create mode 100644 src/lib/includes/date.php create mode 100644 src/lib/includes/framework.php create mode 100644 src/lib/includes/ical.php create mode 100644 src/lib/includes/index.html create mode 100644 src/lib/includes/recurringdate.php create mode 100644 src/lib/includes/timezone.php create mode 100644 src/lib/zapcallib.php diff --git a/favicon.ico b/public/favicon.ico similarity index 100% rename from favicon.ico rename to public/favicon.ico diff --git a/images/background.jpg b/public/images/background.jpg similarity index 100% rename from images/background.jpg rename to public/images/background.jpg diff --git a/images/arrow-up.svg b/public/images/icons/arrow-up.svg similarity index 100% rename from images/arrow-up.svg rename to public/images/icons/arrow-up.svg diff --git a/public/images/icons/bars.svg b/public/images/icons/bars.svg new file mode 100644 index 0000000..c144b77 --- /dev/null +++ b/public/images/icons/bars.svg @@ -0,0 +1,2 @@ + + diff --git a/images/user-times.svg b/public/images/icons/user-times.svg similarity index 100% rename from images/user-times.svg rename to public/images/icons/user-times.svg diff --git a/public/images/icons/users.svg b/public/images/icons/users.svg new file mode 100644 index 0000000..977f529 --- /dev/null +++ b/public/images/icons/users.svg @@ -0,0 +1,2 @@ + + diff --git a/images/lageplan.svg b/public/images/lageplan.svg similarity index 100% rename from images/lageplan.svg rename to public/images/lageplan.svg diff --git a/index.html b/public/index.html similarity index 98% rename from index.html rename to public/index.html index 0ed1843..50f77c4 100644 --- a/index.html +++ b/public/index.html @@ -38,7 +38,9 @@
\n"; + echo "
\n"; + return true; +} + +/** + * Die Startfuktion für die Ausgabe der Termine als Tabelle in termine.php + */ + +function printEventTable(): bool +{ + $events = initEvents(); + printTableHead(); + foreach ($events as $event) + { + $event_array = getEventArray($event); + printTableItem($event_array); + } + echo "\t\n"; + echo "\n"; + return true; +} + +function printTableHead() +{ + echo "\n\r\n"; + echo "\t\n"; + echo "\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\n"; + echo "\t\n"; + echo "\t\n"; +} + +function printTableItem($event_array): bool +{ + date_default_timezone_set("UTC"); + $time = $event_array['DTSTART']; + $helper = new ZDateHelper(); + $unix = $helper->fromiCaltoUnixDateTime($time); + $event_date = date('d.m.Y', $unix); + $event_time = date('H:i', $unix); + $event_day = toGerman(date('l', $unix)); + $event_uid = $event_array['UID']; + $event_url = lowerURL($event_array['URL']); + $event_title = trim($event_array['SUMMARY'], '"'); + $event_descr = trim($event_array['DESCRIPTION'], '"'); + $event_location = trim($event_array['LOCATION'], '"'); + echo "\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + echo "\t\t\n"; + return true; +} + +function printURL(string $url): bool +{ + $stripped = trim($url, '"'); + if ($url != '') + { + echo "
" . $stripped . "\n"; + } + else + { + echo "\n"; + } + return true; +} + +/** + * Funktionen, die von beiden Ausgaben benutzt werden. + */ + +/** + * @return ZCiCalNode[]|null + */ +function initEvents(): ?array +/** + * Allgemeingültige Funktion zur Initialisierung. Enthält die Schritte, die + * von beiden Ausgaben gleichermaßen gebraucht werden. + * - Erstellen des iCalendar Objekts (initCalendar). + * - Schaut, ob der Kalender überhaupt Events enthält (printEventCount). + * - Sammel alle Events in einer Liste (grabEvents). + * Gibt zweidimmensionales assoziatives Array oder Null zurück. + */ +{ + $iCalObj = initCalendar(); + if (!isset ($iCalObj)) + { + printError("Fehler beim Initialisieren des Kalenders"); + return null; + } + $count = printEventCount($iCalObj); + if ($count == 0 or $count == false) + { + return null; + } + $events = grabEvents($iCalObj); + return $events; +} + +function initCalendar(): ?ZCiCal +/** + * Erstellt das Kalenderobjekt vom Typ ZCiCal. + * Gibt das Kalenderobjekt oder Null zurück. + */ +{ + $iCalFile = '../public/krautspace.ics'; + $iCalString = file_get_contents($iCalFile); + if ($iCalString == false) + { + printError("Kann Kalenderdatei nicht lesen"); + return null; + } + $iCalObj = new ZCiCal($iCalString); + return $iCalObj; +} + +function printEventCount(ZCiCal $iCalObj): ?int +/** + * Gibt die Anzahl der Events zurück, die das übergebene + * Kalenderobjekt enthält. Im Fehlerfall wird Null zurück + * gegeben. + */ +{ + $eventCount = $iCalObj->countEvents(); + if (!isset ($eventCount)) + { + printError("Fehler beim Parsen des Kalenders"); + return null; + } + // echo "

$eventCount anstehende Events

"; + return $eventCount; +} + +function grabEvents(ZCiCal $iCalObj): ?array +{ +/** + * Läuft durch das iCalendar objekt und sammelt alle Nodes vom Typ + * 'VEVENT' ein. Gibt ein Array mit Objekten vom Typ 'ZCiCalNode' zurück. + * Im Fehlerfall wird Null zurück gegeben. + */ + $events = []; + if (isset ($iCalObj->tree->child)) + { + foreach ($iCalObj->tree->child as $node) + { + if ($node->getName() == "VEVENT") + { + $events[] = $node; + } + } + } + else + { + printError("Cant find nodes"); + return null; + } + return $events; +} + +function getEventArray(ZCiCalNode $node): array +{ +/** + * Bekommt eine Event Node vom Typ 'ZiCalNode' übergeben und extrahiert + * daraus die gewünschten Elemente. Bildet daraus ein zweidimmensionales + * assoziatives Array. Gibt dieses Array zurück. + * Wird derzeit nur von der tabellarischen Ausgabe referenziert, welche + + momentan nicht genutzt wird. + */ + /** + * @var ZCiCalNode $node + * @var ZCiCalDataNode $event + */ + $event = $node->data; + $event_array = []; + $keys = array('DTSTART', 'SUMMARY', 'DESCRIPTION', 'URL', 'LOCATION'); + + foreach ($keys as $key) + { + $event_array[$key] = $event[$key]->value[0]; + if ($key === 'DTSTART') + { + if (isset ($event[$key]->parameter['tzid'])) + { + $event_array['TZ'] = $event[$key]->parameter['tzid']; + } + } + else if ($key === 'URL') + { + $event_array[$key] = $event[$key]->parameter['value']; + } + } + return $event_array; +} + +function printError($errMsg) +{ + echo "\n\r

$errMsg

\n\r"; + return true; +} + +function toGerman(string $day): string +{ + switch ($day) + { + case 'Monday': + return 'Montag'; + case 'Tuesday': + return 'Dienstag'; + case 'Wednesday': + return 'Mittwoch'; + case 'Thursday': + return 'Donnerstag'; + case 'Friday': + return 'Freitag'; + case 'Saturday': + return 'Samstag'; + case 'Sunday': + return 'Sonntag'; + default: + return '?'; + } +} + +function lowerURL(string $url): string +{ + $old = array('HTTPS', 'HTTP', 'FTP', 'WWW', 'SSH'); + $new = array('https', 'http', 'ftp', 'www', 'ssh'); + $new_url = str_replace($old, $new, $url); + return $new_url; +} + +function calculateNextStart(int $unix_start, string $rrule): int +/** + * Berechnet für wiederkehrende Termine den aktuell nächsten Termin. + * dabei werden zur Zeit nur der der Zeitraum zwischen zwei Terminen + * und eine mögliche Anzahl der Termine berücksichtigt. Gibt den neuen + * Termin als Unix-Zeitstempel zurück. + */ +{ + $counter = 0; + + $rule_array = getRuleArray($rrule); + if (isset ($rule_array['COUNT'])) + { + $count = $rule_array['COUNT']; + } + if (isset ($rule_array['FREQ'])) + { + $frequency = $rule_array['FREQ']; + } + if (isset ($rule_array['INTERVAL'])) + { + $interval = $rule_array['INTERVAL']; + } + if (isset ($rule_array['UNTIL'])) + { + //todo implement + $until_string = $rule_array['UNTIL']; + } + + $freq_offset = getOffset($frequency); + if (isset ($interval)) + { + $offset = $freq_offset * $interval; + } + else + { + $offset = $freq_offset; + } + while ($unix_start <= time()) + { + if (isset ($count)) + { + if ($counter >= $count) + { + break; + } + } + $unix_start = $unix_start + $offset; + $counter = $counter + 1; + } + return $unix_start; +} + +function getOffset(string $frequence): int +{ + switch ($frequence) + { + case 'HOURLY': + return 3600; + case 'DAILY': + return 86400; + case 'WEEKLY': + return 604800; + default: + return 1; + } +} + +function getRuleArray(string $rrule): array +/** + * Zerlegt den String einer RRULE und gibt die einzelnen Elemente als + * assoziatives Array zurück. + */ +{ + $rule_array = []; + $rule_strings = explode(';', $rrule); + foreach ($rule_strings as $r_string) + { + $rule = explode('=', $r_string); + $rule_array[$rule[0]] = $rule[1]; + } + return $rule_array; +} + +function EventIsPast(int $unix_start): bool +/** + * Prüft, ob die übergebenen Unixzeit älter als der aktuelle Tag ist. Gibt + * Wahr oder Falsch zurück. + */ +{ + $event_date = date('d.m.Y', $unix_start); + $day_end = strtotime($event_date) + 86400; + $actual_date = time(); + + if ($day_end < $actual_date) + { + return true; + } + return false; +} + +/** + * Bekommt zwei Eventnodes übergeben und vergleicht deren Startdatum. Die + * Funktion wird intern von ausort() benutzt, um das Array mit den Eventnodes + * nach Datum zu sortieren. + * + * @param ZCiCalNode $event_a + * @param ZCiCalNode $event_b + * @return int + */ +function compareEventStart(ZCiCalNode $event_a, ZCiCalNode $event_b): int +{ + $a = $event_a->data['DTSTART']->value[0]; + $b = $event_b->data['DTSTART']->value[0]; + return $a <=> $b; +} diff --git a/src/lib/README.md b/src/lib/README.md new file mode 100644 index 0000000..11e64f6 --- /dev/null +++ b/src/lib/README.md @@ -0,0 +1,127 @@ +# Zap Calendar iCalendar Library + +(https://github.com/zcontent/icalendar) + +The Zap Calendar iCalendar Library is a PHP library for supporting the iCalendar (RFC 5545) standard. + +This PHP library is for reading and writing iCalendar formatted feeds and +files. Features of the library include: + +- Read AND write support for iCalendar files +- Object based creation and manipulation of iCalendar files +- Supports expansion of RRULE to a list of repeating dates +- Supports adding timezone info to iCalendar file + +All iCalendar data is stored in a PHP object tree. +This allows any property to be added to the iCalendar feed without +requiring specialized library function calls. +With power comes responsibility. Missing or invalid properties can cause +the resulting iCalendar file to be invalid. Visit [iCalendar.org](http://icalendar.org) to view valid +properties and test your feed using the site's [iCalendar validator tool](http://icalendar.org/validator.html). + +Library API documentation can be found at http://icalendar.org/zapcallibdocs + +See the examples folder for programs that read and write iCalendar +files. At its simpliest, you need to include the library at the top of your program: + +```php +require_once($path_to_library . "/zapcallib.php"); +``` + +Create an ical object using the ZCiCal object: + +```php +$icalobj = new ZCiCal(); +``` + +Add an event object: + +```php +$eventobj = new ZCiCalNode("VEVENT", $icalobj->curnode); +``` + +Add a start and end date to the event: + +```php +// add start date +$eventobj->addNode(new ZCiCalDataNode("DTSTART:" . ZCiCal::fromSqlDateTime("2020-01-01 12:00:00"))); + +// add end date +$eventobj->addNode(new ZCiCalDataNode("DTEND:" . ZCiCal::fromSqlDateTime("2020-01-01 13:00:00"))); +``` + +Write the object in iCalendar format using the export() function call: + +```php +echo $icalobj->export(); +``` + +This example will not validate since it is missing some required elements. +Look at the simpleevent.php example for the minimum # of elements +needed for a validated iCalendar file. + +To create a multi-event iCalendar file, simply create multiple event objects. For example: + +```php +$icalobj = new ZCiCal(); +$eventobj1 = new ZCiCalNode("VEVENT", $icalobj->curnode); +$eventobj1->addNode(new ZCiCalDataNode("SUMMARY:Event 1")); +... +$eventobj2 = new ZCiCalNode("VEVENT", $icalobj->curnode); +$eventobj2->addNode(new ZCiCalDataNode("SUMMARY:Event 2")); +... +``` + +To read an existing iCalendar file/feed, create the ZCiCal object with a string representing the contents of the iCalendar file: + +```php +$icalobj = new ZCiCal($icalstring); +``` + +Large iCalendar files can be read in chunks to reduce the amount of memory needed to hold the iCalendar feed in memory. This example reads 500 events at a time: + +```php +$icalobj = null; +$eventcount = 0; +$maxevents = 500; +do +{ + $icalobj = newZCiCal($icalstring, $maxevents, $eventcount); + ... + $eventcount +=$maxevents; +} +while($icalobj->countEvents() >= $eventcount); +``` + +You can read the events from an imported (or created) iCalendar object in this manner: + +```php +foreach($icalobj->tree->child as $node) +{ + if($node->getName() == "VEVENT") + { + foreach($node->data as $key => $value) + { + if($key == "SUMMARY") + { + echo "event title: " . $value->getValues() . "\n"; + } + } + } +} +``` + +## Known Limitations + +- Since the library utilizes objects to read and write iCalendar data, the +size of the iCalendar data is limited to the amount of available memory on the machine. +The ZCiCal() object supports reading a range of events to minimize memory +space. +- The library ignores timezone info when importing files, instead utilizing PHP's timezone +library for calculations (timezones are supported when exporting files). +Imported timezones need to be aliased to a [PHP supported timezone](http://php.net/manual/en/timezones.php). +- At this time, the library does not support the "BYSETPOS" option in RRULE items. +- At this time, the maximum date supported is 2036 to avoid date math issues +with 32 bit systems. +- Repeating events are limited to a maximum of 5,000 dates to avoid memory or infinite loop issues + diff --git a/src/lib/includes/date.php b/src/lib/includes/date.php new file mode 100644 index 0000000..4f9e7e6 --- /dev/null +++ b/src/lib/includes/date.php @@ -0,0 +1,568 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Date Helper Class + * + * Helper class for various date functions + */ +class ZDateHelper { + + /** + * Find the number of days in a month + * + * @param int $month Month is between 1 and 12 inclusive + * + * @param int $year is between 1 and 32767 inclusive + * + * @return int + */ + static function DayInMonth($month, $year) { + $daysInMonth = array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); + if ($month != 2) return $daysInMonth[$month - 1]; + return (checkdate($month, 29, $year)) ? 29 : 28; + } + + /** + * Is given date today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isToday($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now); + } + + /** + * Is given date before today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isBeforeToday($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) > + mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); + } + + /** + * Is given date after today? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isAfterToday($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return mktime(0,0,0,date('m',$now),date('d',$now),date('Y',$now)) < + mktime(0,0,0,date('m',$date),date('d',$date),date('Y',$now)); + } + + /** + * Is given date tomorrow? + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isTomorrow($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return gmdate('Y-m-d', $date) == gmdate('Y-m-d', $now + 60 * 60 * 24); + } + + /** + * Is given date in the future? + * + * This routine differs from isAfterToday() in that isFuture() will + * return true for date-time values later in the same day. + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isFuture($date, $tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $date > $now; + } + + /** + * Is given date in the past? + * + * This routine differs from isBeforeToday() in that isPast() will + * return true for date-time values earlier in the same day. + * + * @param int $date date in Unix timestamp format + * + * @param int $tzid PHP recognized timezone (default is UTC) + * + * @return bool + */ + static function isPast($date, $tzid = "UTC") { + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $date < $now; + } + + /** + * Return current Unix timestamp in local timezone + * + * @param string $tzid PHP recognized timezone + * + * @return int + */ + static function now($tzid = "UTC"){ + $dtz = new DateTimeZone($tzid); + $dt = new DateTime("now", $dtz); + $now = time() + $dtz->getOffset($dt); + return $now; + } + + /** + * Is given date fall on a weekend? + * + * @param int $date Unix timestamp + * + * @return bool + */ + static function isWeekend($date) { + $dow = gmdate('w',$date); + return $dow == 0 || $dow == 6; + } + + /** + * Format Unix timestamp to SQL date-time + * + * @param int $t Unix timestamp + * + * @return string + */ + static function toSqlDateTime($t = 0) + { + date_default_timezone_set('GMT'); + if($t == 0) + return gmdate('Y-m-d H:i:s',self::now()); + return gmdate('Y-m-d H:i:s', $t); + } + + /** + * Format Unix timestamp to SQL date + * + * @param int $t Unix timestamp + * + * @return string + */ + static function toSqlDate($t = 0) + { + date_default_timezone_set('GMT'); + if($t == 0) + return gmdate('Y-m-d',self::now()); + return gmdate('Y-m-d', $t); + } + + /** + * Format iCal date-time string to Unix timestamp + * + * @param string $datetime in iCal time format ( YYYYMMDD or YYYYMMDDTHHMMSS or YYYYMMDDTHHMMSSZ ) + * + * @return int Unix timestamp + */ + static function fromiCaltoUnixDateTime($datetime) { + // first check format + $formats = array(); + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/"; + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]/"; + $formats[] = "/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]Z/"; + $ok = false; + foreach($formats as $format){ + if(preg_match($format,$datetime)){ + $ok = true; + break; + } + } + if(!$ok) + return null; + $year = substr($datetime,0,4); + $month = substr($datetime,4,2); + $day = substr($datetime,6,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 8 && $datetime[8] == "T") { + $hour = substr($datetime,9,2); + $minute = substr($datetime,11,2); + $second = substr($datetime,13,2); + } + return gmmktime($hour, $minute, $second, $month, $day, $year); + } + + /** + * Format Unix timestamp to iCal date-time string + * + * @param int $datetime Unix timestamp + * + * @return string + */ + static function fromUnixDateTimetoiCal($datetime){ + date_default_timezone_set('GMT'); + return gmdate("Ymd\THis",$datetime); + } + + /** + * Convert iCal duration string to # of seconds + * + * @param string $duration iCal duration string + * + * return int + */ + static function iCalDurationtoSeconds($duration) { + $secs = 0; + if($duration[0] == "P") { + $duration = str_replace(array("H","M","S","T","D","W","P"),array("H,","M,","S,","","D,","W,",""),$duration); + $dur2 = explode(",",$duration); + foreach($dur2 as $dur){ + $val=intval($dur); + if(strlen($dur) > 0){ + switch($dur{strlen($dur) - 1}) { + case "H": + $secs += 60*60 * $val; + break; + case "M": + $secs += 60 * $val; + break; + case "S": + $secs += $val; + break; + case "D": + $secs += 60*60*24 * $val; + break; + case "W": + $secs += 60*60*24*7 * $val; + break; + } + } + } + } + return $secs; + } + + /** + * Check if day falls within date range + * + * @param int $daystart start of day in Unix timestamp format + * + * @param int $begin Unix timestamp of starting date range + * + * @param int $end Unix timestamp of end date range + * + * @return bool + */ + static function inDay($daystart, $begin, $end) + { + //$dayend = $daystart + 60*60*24 - 60; + // add 1 day to determine end of day + // don't use 24 hours, since twice a year DST Sundays are 23 hours and 25 hours in length + // adding 1 day takes this into account + $dayend = self::addDate($daystart, 0,0,0,0,1,0); + + $end = max($begin, $end); // $end can't be less than $begin + $inday = + ($daystart <= $begin && $begin < $dayend) + ||($daystart < $end && $end < $dayend) + ||($begin <= $daystart && $end > $dayend) + ; + return $inday; + } + + /** + * Convert SQL date or date-time to Unix timestamp + * + * @param string $datetime SQL date or date-time + * + * @return int Unix date-time timestamp + */ + static function toUnixDate($datetime) + { + $year = substr($datetime,0,4); + $month = substr($datetime,5,2); + $day = substr($datetime,8,2); + + return mktime(0, 0, 0, $month, $day, $year); + } + + /** + * Convert SQL date or date-time to Unix date timestamp + * + * @param string $datetime SQL date or date-time + * + * @return int Unix timestamp + */ + static function toUnixDateTime($datetime) + { + // convert to absolute dates if neccessary + $datetime = self::getAbsDate($datetime); + $year = substr($datetime,0,4); + $month = substr($datetime,5,2); + $day = substr($datetime,8,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 10) { + $hour = substr($datetime,11,2); + $minute = substr($datetime,14,2); + $second = substr($datetime,17,2); + } + return gmmktime($hour, $minute, $second, $month, $day, $year); + } + + /** + * Date math: add or substract from current date to get a new date + * + * @param int $date date to add or subtract from + * + * @param int $hour add or subtract hours from date + * + * @param int $min add or subtract minutes from date + * + * @param int $sec add or subtract seconds from date + * + * @param int $month add or subtract months from date + * + * @param int $day add or subtract days from date + * + * @param int $year add or subtract years from date + * + * @param string $tzid PHP recognized timezone (default is UTC) + */ + static function addDate($date, $hour, $min, $sec, $month, $day, $year, $tzid = "UTC") { + date_default_timezone_set($tzid); + $sqldate = self::toSQLDateTime($date); + $tdate = array(); + $tdate["year"] = substr($sqldate,0,4); + $tdate["mon"] = substr($sqldate,5,2); + $tdate["mday"] = substr($sqldate,8,2); + $tdate["hours"] = substr($sqldate,11,2); + $tdate["minutes"] = substr($sqldate,14,2); + $tdate["seconds"] = substr($sqldate,17,2); + $newdate=mktime($tdate["hours"] + $hour, $tdate["minutes"] + $min, $tdate["seconds"] + $sec, $tdate["mon"] + $month, $tdate["mday"] + $day, $tdate["year"] + $year); + date_default_timezone_set("UTC"); + //echo self::toSQLDateTime($date) . " => " . self::toSQLDateTime($newdate) . " ($hour:$min:$sec $month/$day/$year)
\n"; + return $newdate; + } + + /** + * Date math: get date from week and day in specifiec month + * + * This routine finds actual dates for the second Tuesday of the month, last Friday of the month, etc. + * For second Tuesday, use $week = 1, $wday = 2 + * for last Friday, use $week = -1, $wday = 5 + * + * @param int $date Unix timestamp + * + * @param int $week week number, 0 is first week, -1 is last + * + * @param int $wday day of week, 0 is Sunday, 6 is Saturday + * + * @param string $tzid PHP supported timezone + * + * @return int Unix timestamp + */ + static function getDateFromDay($date, $week, $wday,$tzid="UTC") { + //echo "getDateFromDay(" . self::toSqlDateTime($date) . ",$week,$wday)
\n"; + // determine first day in month + $tdate = getdate($date); + $monthbegin = gmmktime(0,0,0, $tdate["mon"],1,$tdate["year"]); + $monthend = self::addDate($monthbegin, 0,0,0,1,-1,0,$tzid); // add 1 month and subtract 1 day + $day = self::addDate($date,0,0,0,0,1 - $tdate["mday"],0,$tzid); + $month = array(array()); + while($day <= $monthend) { + $tdate=getdate($day); + $month[$tdate["wday"]][]=$day; + //echo self::toSQLDateTime($day) . "
\n"; + $day = self::addDate($day, 0,0,0,0,1,0,$tzid); // add 1 day + } + $dayinmonth=0; + if($week >= 0) + $dayinmonth = $month[$wday][$week]; + else + $dayinmonth = $month[$wday][count($month[$wday]) - 1]; + //echo "return " . self::toSQLDateTime($dayinmonth); + //exit; + return $dayinmonth; + } + + /** + * Convert UTC date-time to local date-time + * + * @param string $sqldate SQL date-time string + * + * @param string $tzid PHP recognized timezone (default is "UTC") + * + * @return string SQL date-time string + */ + static function toLocalDateTime($sqldate, $tzid = "UTC" ){ + try + { + $timezone = new DateTimeZone($tzid); + } + catch(Exception $e) + { + // bad time zone specified + return $sqldate; + } + $udate = self::toUnixDateTime($sqldate); + $daydatetime = new DateTime("@" . $udate); + $tzoffset = $timezone->getOffset($daydatetime); + return self::toSqlDateTime($udate + $tzoffset); + } + + /** + * Convert local date-time to UTC date-time + * + * @param string $sqldate SQL date-time string + * + * @param string $tzid PHP recognized timezone (default is "UTC") + * + * @return string SQL date-time string + */ + static function toUTCDateTime($sqldate, $tzid = "UTC" ){ + + date_default_timezone_set("UTC"); + try + { + $date = new DateTime($sqldate, $tzid); + } + catch(Exception $e) + { + // bad time zone specified + return $sqldate; + } + $offset = $date->getOffsetFromGMT(); + if($offset >= 0) + $date->sub(new DateInterval("PT".$offset."S")); + else + $date->add(new DateInterval("PT".abs($offset)."S")); + return $date->toSql(true); + } + + /** + * Convert from a relative date to an absolute date + * + * Examples of relative dates are "-2y" for 2 years ago, "18m" + * for 18 months after today. Relative date uses "y", "m" and "d" for + * year, month and day. Relative date can be combined into comma + * separated list, i.e., "-1y,-1d" for 1 year and 1 day ago. + * + * @param string $date relative date string (i.e. "1y" for 1 year from today) + * + * @param string $rdate reference date, or blank for current date (in SQL date-time format) + * + * @return string in SQL date-time format + */ + static function getAbsDate($date,$rdate = ""){ + if(str_replace(array("y","m","d","h","n"),"",strtolower($date)) != strtolower($date)){ + date_default_timezone_set("UTC"); + if($rdate == "") + $udate = time(); + else + $udate = self::toUnixDateTime($rdate); + $values=explode(",",strtolower($date)); + $y = 0; + $m = 0; + $d = 0; + $h = 0; + $n = 0; + foreach($values as $value){ + $rtype = substr($value,strlen($value)-1); + $rvalue = intval(substr($value,0,strlen($value) - 1)); + switch($rtype){ + case 'y': + $y = $rvalue; + break; + case 'm': + $m = $rvalue; + break; + case 'd': + $d = $rvalue; + break; + case 'h': + $h = $rvalue; + break; + case 'n': + $n = $rvalue; + break; + } + // for "-" values, move to start of day , otherwise, move to end of day + if($rvalue[0] == '-') + $udate = mktime(0,0,0,date('m',$udate),date('d',$udate),date('Y',$udate)); + else + $udate = mktime(0,-1,0,date('m',$udate),date('d',$udate)+1,date('Y',$udate)); + $udate = self::addDate($udate,$h,$n,0,$m,$d,$y); + } + $date = self::toSqlDateTime($udate); + } + return $date; + } + + /** + * Format Unix timestamp to iCal date-time format + * + * @param int $datetime Unix timestamp + * + * @return string iCal date-time string + */ + static function toiCalDateTime($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return gmdate("Ymd\THis",$datetime); + } + + /** + * Format Unix timestamp to iCal date format + * + * @param int $datetime Unix timestamp + * + * @return string iCal date-time string + */ + static function toiCalDate($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return gmdate("Ymd",$datetime); + } +} diff --git a/src/lib/includes/framework.php b/src/lib/includes/framework.php new file mode 100644 index 0000000..69df54e --- /dev/null +++ b/src/lib/includes/framework.php @@ -0,0 +1,32 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * set MAXYEAR to 2036 for 32 bit systems, can be higher for 64 bit systems + * + * @var integer + */ +define('_ZAPCAL_MAXYEAR', 2036); + +/** + * set MAXREVENTS to maximum # of repeating events + * + * @var integer + */ +define('_ZAPCAL_MAXREVENTS', 5000); + +require_once(_ZAPCAL_BASE . '/includes/date.php'); +require_once(_ZAPCAL_BASE . '/includes/recurringdate.php'); +require_once(_ZAPCAL_BASE . '/includes/ical.php'); +require_once(_ZAPCAL_BASE . '/includes/timezone.php'); diff --git a/src/lib/includes/ical.php b/src/lib/includes/ical.php new file mode 100644 index 0000000..1b89244 --- /dev/null +++ b/src/lib/includes/ical.php @@ -0,0 +1,986 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Object for storing an unfolded iCalendar line + * + * The ZCiCalDataNode class contains data from an unfolded iCalendar line + * + */ +class ZCiCalDataNode { + /** + * The name of the node + * + * @var string + */ + var $name = ""; + + /** + * Node parameters (before the colon ":") + * + * @var array + */ + var $parameter=array(); + + /** + * Node values (after the colon ":") + * + * @var array + */ + var $value=array(); + + /** + * Create an object from an unfolded iCalendar line + * + * @param string $line An unfolded iCalendar line + * + * @return void + * + */ + function __construct( $line ) { + //echo "ZCiCalDataNode($line)
\n"; + //separate line into parameters and value + // look for colon separating name or parameter and value + // first change any escaped colons temporarily to make it easier + $tline = str_replace("\\:", "`~", $line); + // see if first colon is inside a quoted string + $i = 0; + $datafind = false; + $inquotes = false; + while(!$datafind && ($i < strlen($tline))) { + //echo "$i: " . $tline[$i] . ", ord() = " . ord($tline{$i}) . "
\n"; + if(!$inquotes && $tline[$i] == ':') + $datafind=true; + else{ + $i += 1; + if(substr($tline,$i,1) == '"') + $inquotes = !$inquotes; + } + } + if($datafind){ + $value = str_replace("`~","\\:",substr($line,$i+1)); + // fix escaped characters (don't see double quotes in spec but Apple apparently uses it in iCal) + $value = str_replace(array('\\N' , '\\n', '\\"' ), array("\n", "\n" , '"'), $value); + $tvalue = str_replace("\\,", "`~", $value); + //echo "value: " . $tvalue . "
\n"; + $tvalue = explode(",",$tvalue); + $value = str_replace("`~","\\,",$tvalue); + $this->value = $value; + } + + $parameter = trim(substr($line,0,$i)); + + $parameter = str_replace("\\;", "`~", $parameter); + $parameters = explode(";", $parameter); + $parameters = str_replace("`~", "\\;", $parameters); + $this->name = array_shift($parameters); + foreach($parameters as $parameter){ + $pos = strpos($parameter,"="); + if($pos > 0){ + $param = substr($parameter,0,$pos); + $paramvalue = substr($parameter,$pos+1); + $tvalue = str_replace("\\,", "`~", $paramvalue); + //$tvalue = explode(",",$tvalue); + $paramvalue = str_replace("`~","\\,",$tvalue); + $this->parameter[strtolower($param)] = $paramvalue; + //$this->paramvalue[] = $paramvalue; + } + } + } + +/** + * getName() + * + * Return the name of the object + * + * @return string + */ + function getName(){ + return $this->name; + } + +/** + * Get $ith parameter from array + * @param int $i + * + * @return var + */ + function getParameter($i){ + return $this->parameter[$i]; + } + +/** + * Get parameter array + * + * @return array + */ + function getParameters(){ + return $this->parameter; + } + +/** + * Get comma separated values + * + * @return string + */ + function getValues(){ + return implode(",",$this->value); + } +} + +/** + * Object for storing a list of unfolded iCalendar lines (ZCiCalDataNode objects) + * + * @property object $parentnode Parent of this node + * + * @property array $child Array of children for this node + * + * @property data $data Array of data for this node + * + * @property object $next Next sibling of this node + * + * @property object $prev Previous sibling of this node + */ + +class ZCiCalNode { + /** + * The name of the node + * + * @var string + */ + var $name=""; + + /** + * The parent of this node + * + * @var object + */ + var $parentnode=null; + + /** + * Array of children for this node + * + * @var array + */ + var $child= array(); + + /** + * Array of $data for this node + * + * @var array + */ + var $data= array(); + + + /** + * Next sibling of this node + * + * @var object + */ + var $next=null; + + /** + * Previous sibling of this node + * + * @var object + */ + var $prev=null; + + /** + * Create ZCiCalNode + * + * @param string $_name Name of node + * + * @param object $_parent Parent node for this node + * + * @param bool $first Is this the first child for this parent? + */ + function __construct( $_name, & $_parent, $first = false) { + $this->name = $_name; + $this->parentnode = $_parent; + if($_parent != null){ + if(count($this->parentnode->child) > 0) { + if($first) + { + $first = & $this->parentnode->child[0]; + $first->prev = & $this; + $this->next = & $first; + } + else + { + $prev =& $this->parentnode->child[count($this->parentnode->child)-1]; + $prev->next =& $this; + $this->prev =& $prev; + } + } + if($first) + { + array_unshift($this->parentnode->child, $this); + } + else + { + $this->parentnode->child[] =& $this; + } + } + /* + echo "creating " . $this->getName(); + if($_parent != null) + echo " child of " . $_parent->getName() . "/" . count($this->parentnode->child); + echo "
"; + */ + } + + /** + * Return the name of the object + * + * @return string + */ + function getName() { + return $this->name; + } + + /** + * Add node to list + * + * @param object $node + * + */ + function addNode($node) { + if(array_key_exists($node->getName(), $this->data)) + { + if(!is_array($this->data[$node->getName()])) + { + $this->data[$node->getName()] = array($this->data[$node->getName()]); + } + $this->data[$node->getName()][] = $node; + } + else + { + $this->data[$node->getName()] = $node; + } + } + + /** + * Get Attribute + * + * @param int $i array id of attribute to get + * + * @return string + */ + function getAttrib($i) { + return $this->attrib[$i]; + } + + /** + * Set Attribute + * + * @param string $value value of attribute to set + * + */ + function setAttrib($value) { + $this->attrib[] = $value; + } + + /** + * Get the parent object of this object + * + * @return object parent of this object + */ + function &getParent() { + return $this->parentnode; + } + + /** + * Get the first child of this object + * + * @return object The first child + */ + function &getFirstChild(){ + static $nullguard = null; + if(count($this->child) > 0) { + //echo "moving from " . $this->getName() . " to " . $this->child[0]->getName() . "
"; + return $this->child[0]; + } + else + return $nullguard; + } + + /** + * Print object tree in HTML for debugging purposes + * + * @param object $node select part of tree to print, or leave blank for full tree + * + * @param int $level Level of recursion (usually leave this blank) + * + * @return string - HTML formatted display of object tree + */ + function printTree(& $node=null, $level=1){ + $level += 1; + $html = ""; + if($node == null) + $node = $this->parentnode; + if($level > 5) + { + die("levels nested too deep
\n"); + //return; + } + for($i = 0 ; $i < $level; $i ++) + $html .= "+"; + $html .= $node->getName() . "
\n"; + foreach ($node->child as $c){ + $html .= $node->printTree($c,$level); + } + $level -= 1; + return $html; + } + + /** + * export tree to icalendar format + * + * @param object $node Top level node to export + * + * @param int $level Level of recursion (usually leave this blank) + * + * @return string iCalendar formatted output + */ + function export(& $node=null, $level=0){ + $txtstr = ""; + if($node == null) + $node = $this; + if($level > 5) + { + //die("levels nested too deep
\n"); + throw new Exception("levels nested too deep"); + } + $txtstr .= "BEGIN:" . $node->getName() . "\r\n"; + if(property_exists($node,"data")) + foreach ($node->data as $d){ + if(is_array($d)) + { + foreach ($d as $c) + { + //$txtstr .= $node->export($c,$level + 1); + $p = ""; + $params = @$c->getParameters(); + if(count($params) > 0) + { + foreach($params as $key => $value){ + $p .= ";" . strtoupper($key) . "=" . $value; + } + } + $txtstr .= $this->printDataLine($c, $p); + } + } + else + { + $p = ""; + $params = @$d->getParameters(); + if(count($params) > 0) + { + foreach($params as $key => $value){ + $p .= ";" . strtoupper($key) . "=" . $value; + } + } + $txtstr .= $this->printDataLine($d, $p); + /* + $values = $d->getValues(); + // don't think we need this, Sunbird does not like it in the EXDATE field + //$values = str_replace(",", "\\,", $values); + + $line = $d->getName() . $p . ":" . $values; + $line = str_replace(array("
","
","
","
0) { + $linewidth = ($linecount == 0? 75 : 74); + $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); + if($linecount > 0) + $txtstr .= " "; + $txtstr .= substr($line,0,$linesize) . "\r\n"; + $linecount += 1; + $line = substr($line,$linewidth); + } + */ + } + //echo $line . "\n"; + } + if(property_exists($node,"child")) + foreach ($node->child as $c){ + $txtstr .= $node->export($c,$level + 1); + } + $txtstr .= "END:" . $node->getName() . "\r\n"; + return $txtstr; + } + + /** + * print an attribute line + + * @param object $d attributes + * @param object $p properties + * + */ + function printDataLine($d, $p) + { + $txtstr = ""; + + $values = $d->getValues(); + // don't think we need this, Sunbird does not like it in the EXDATE field + //$values = str_replace(",", "\\,", $values); + + $line = $d->getName() . $p . ":" . $values; + $line = str_replace(array("
","
","
","
0) { + $linewidth = ($linecount == 0? 75 : 74); + $linesize = (strlen($line) > $linewidth? $linewidth: strlen($line)); + if($linecount > 0) + $txtstr .= " "; + $txtstr .= substr($line,0,$linesize) . "\r\n"; + $linecount += 1; + $line = substr($line,$linewidth); + } + return $txtstr; + } +} + +/** + * + * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. + * +*/ +class ZCiCal { + /** + * The root node of the object tree + * + * @var object + */ + var $tree=null; + /** + * The most recently created node in the tree + * + * @var object + */ + var $curnode=null; + +/** + * The main iCalendar object containing ZCiCalDataNodes and ZCiCalNodes. + * + * use maxevents and startevent to read events in multiple passes (to save memory) + * + * @param string $data icalendar feed string (empty if creating new feed) + * + * @param int $maxevents maximum # of events to read + * + * @param int $startevent starting event to read + * + * @return void + * +* +*/ +function __construct($data = "", $maxevents = 1000000, $startevent = 0) { + + if($data != ""){ + // unfold lines + // first change all eol chars to "\n" + $data = str_replace(array("\r\n", "\n\r", "\n", "\r"), "\n", $data); + // now unfold lines + //$data = str_replace(array("\n ", "\n "),"!?", $data); + $data = str_replace(array("\n ", "\n "),"", $data); + // replace special iCal chars + $data = str_replace(array("\\\\","\,"),array("\\",","), $data); + + // parse each line + $lines = explode("\n", $data); + + $linecount = 0; + $eventcount = 0; + $eventpos = 0; + foreach($lines as $line) { + //$line = str_replace("!?", "\n", $line); // add nl back into descriptions + // echo ($linecount + 1) . ": " . $line . "
"; + if(substr($line,0,6) == "BEGIN:") { + // start new object + $name = substr($line,6); + if($name == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + $this->curnode = new ZCiCalNode($name, $this->curnode); + if($this->tree == null) + $this->tree = $this->curnode; + } + } + else + { + $this->curnode = new ZCiCalNode($name, $this->curnode); + if($this->tree == null) + $this->tree = $this->curnode; + } + //echo "new node: " . $this->curnode->name . "
\n"; + /* + if($this->curnode->getParent() != null) + echo "parent of " . $this->curnode->getName() . " is " . $this->curnode->getParent()->getName() . "
"; + else + echo "parent of " . $this->curnode->getName() . " is null
"; + */ + } + else if(substr($line,0,4) == "END:") { + $name = substr($line,4); + if($name == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + $eventcount++; + if($this->curnode->getName() != $name) { + //panic, mismatch in iCal structure + //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + } + if($this->curnode->getParent() != null) { + //echo "moving up from " . $this->curnode->getName() ; + $this->curnode = & $this->curnode->getParent(); + //echo " to " . $this->curnode->getName() . "
"; + //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; + } + } + $eventpos++; + } + else + { + if($this->curnode->getName() != $name) { + //panic, mismatch in iCal structure + //die("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + throw new Exception("Can't read iCal file structure, expecting " . $this->curnode->getName() . " but reading $name instead"); + } + if($this->curnode->getParent() != null) { + //echo "moving up from " . $this->curnode->getName() ; + $this->curnode = & $this->curnode->getParent(); + //echo " to " . $this->curnode->getName() . "
"; + //echo $this->curnode->getName() . " has " . count($this->curnode->child) . " children
"; + } + } + } + else { + $datanode = new ZCiCalDataNode($line); + if($this->curnode->getName() == "VEVENT") + { + if($eventcount < $maxevents && $eventpos >= $startevent) + { + if($datanode->getName() == "EXDATE") + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; + } + } + else + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $tnode = $this->curnode->data[$datanode->getName()]; + $this->curnode->data[$datanode->getName()] = array(); + $this->curnode->data[$datanode->getName()][] = $tnode; + $this->curnode->data[$datanode->getName()][] = $datanode; + } + } + } + } + else + { + if($datanode->getName() == "EXDATE") + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $this->curnode->data[$datanode->getName()]->value[] = $datanode->value[0]; + } + } + else + { + if(!array_key_exists($datanode->getName(),$this->curnode->data)) + { + $this->curnode->data[$datanode->getName()] = $datanode; + } + else + { + $tnode = $this->curnode->data[$datanode->getName()]; + $this->curnode->data[$datanode->getName()] = array(); + $this->curnode->data[$datanode->getName()][] = $tnode; + $this->curnode->data[$datanode->getName()][] = $datanode; + } + } + } + } + $linecount++; + } + } + else { + $name = "VCALENDAR"; + $this->curnode = new ZCiCalNode($name, $this->curnode); + $this->tree = $this->curnode; + $datanode = new ZCiCalDataNode("VERSION:2.0"); + $this->curnode->data[$datanode->getName()] = $datanode; + + $datanode = new ZCiCalDataNode("PRODID:-//ZContent.net//ZapCalLib 1.0//EN"); + $this->curnode->data[$datanode->getName()] = $datanode; + $datanode = new ZCiCalDataNode("CALSCALE:GREGORIAN"); + $this->curnode->data[$datanode->getName()] = $datanode; + $datanode = new ZCiCalDataNode("METHOD:PUBLISH"); + $this->curnode->data[$datanode->getName()] = $datanode; + } +} + +/** + * CountEvents() + * + * Return the # of VEVENTs in the object + * + * @return int + */ + +function countEvents() { + $count = 0; + if(isset($this->tree->child)){ + foreach($this->tree->child as $child){ + if($child->getName() == "VEVENT") + $count++; + } + } + return $count; +} + +/** + * CountVenues() + * + * Return the # of VVENUEs in the object + * + * @return int + */ + +function countVenues() { + $count = 0; + if(isset($this->tree->child)){ + foreach($this->tree->child as $child){ + if($child->getName() == "VVENUE") + $count++; + } + } + return $count; +} + +/** + * Export object to string + * + * This function exports all objects to an iCalendar string + * + * @return string an iCalendar formatted string + */ + +function export() { + return $this->tree->export($this->tree); +} + +/** + * Get first event in object list + * Use getNextEvent() to navigate through list + * + * @return object The first event, or null + */ +function &getFirstEvent() { + static $nullguard = null; + if ($this->countEvents() > 0){ + $child = $this->tree->child[0]; + $event=false; + while(!$event && $child != null){ + if($child->getName() == "VEVENT") + $event = true; + else + $child = $child->next; + } + return $child; + } + else + return $nullguard; +} + +/** + * Get next event in object list + * + * @param object $event The current event object + * + * @return object Returns the next event or null if past last event + */ +function &getNextEvent($event){ + do{ + $event = $event->next; + } while($event != null && $event->getName() != "VEVENT"); + return $event; +} + +/** + * Get first venue in object list + * Use getNextVenue() to navigate through list + * + * @return object The first venue, or null + */ +function &getFirstVenue() { + static $nullguard = null; + if ($this->countVenues() > 0){ + $child = $this->tree->child[0]; + $event=false; + while(!$event && $child != null){ + if($child->getName() == "VVENUE") + $event = true; + else + $child = $child->next; + } + return $child; + } + else + return $nullguard; +} + +/** + * Get next venue in object list + * + * @param object $venue The current venue object + * + * @return object Returns the next venue or null if past last venue + */ +function &getNextVenue($venue){ + do{ + $venue = $venue->next; + } while($venue != null && $venue->getName() != "VVENUE"); + return $venue; +} + +/** + * Get first child in object list + * Use getNextSibling() and getPreviousSibling() to navigate through list + * + * @param object $thisnode The parent object + * + * @return object The child object + */ +function &getFirstChild(& $thisnode){ + $nullvalue = null; + if(count($thisnode->child) > 0) { + //echo "moving from " . $thisnode->getName() . " to " . $thisnode->child[0]->getName() . "
"; + return $thisnode->child[0]; + } + else + return $nullvalue; +} + +/** + * Get next sibling in object list + * + * @param object $thisnode The current object + * + * @return object Returns the next sibling + */ +function &getNextSibling(& $thisnode){ + return $thisnode->next; +} + +/** + * Get previous sibling in object list + * + * @param object $thisnode The current object + * + * @return object Returns the previous sibling + */ +function &getPrevSibling(& $thisnode){ + return $thisnode->prev; +} + +/** + * Read date/time in iCal formatted string + * + * @param string iCal formated date/time string + * + * @return int Unix timestamp + * @deprecated Use ZDateHelper::toUnixDateTime() instead + */ + +function toUnixDateTime($datetime){ + $year = substr($datetime,0,4); + $month = substr($datetime,4,2); + $day = substr($datetime,6,2); + $hour = 0; + $minute = 0; + $second = 0; + if(strlen($datetime) > 8 && $datetime[8] == "T") { + $hour = substr($datetime,9,2); + $minute = substr($datetime,11,2); + $second = substr($datetime,13,2); + } + $d1 = mktime($hour, $minute, $second, $month, $day, $year); + +} + +/** + * fromUnixDateTime() + * + * Take Unix timestamp and format to iCal date/time string + * + * @param int $datetime Unix timestamp, leave blank for current date/time + * + * @return string formatted iCal date/time string + * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead + */ + +static function fromUnixDateTime($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return date("Ymd\THis",$datetime); +} + + +/** + * fromUnixDate() + * + * Take Unix timestamp and format to iCal date string + * + * @param int $datetime Unix timestamp, leave blank for current date/time + * + * @return string formatted iCal date string + * @deprecated Use ZDateHelper::fromUnixDateTimetoiCal() instead + */ + +static function fromUnixDate($datetime = null){ + date_default_timezone_set('UTC'); + if($datetime == null) + $datetime = time(); + return date("Ymd",$datetime); +} + +/** + * Format into iCal time format from SQL date or SQL date-time format + * + * @param string $datetime SQL date or SQL date-time string + * + * @return string iCal formatted string + * @deprecated Use ZDateHelper::fromSqlDateTime() instead + */ +static function fromSqlDateTime($datetime = ""){ + if($datetime == "") + $datetime = ZDateHelper::toSqlDateTime(); + if(strlen($datetime) > 10) + return sprintf('%04d%02d%02dT%02d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), + substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); + else + return sprintf('%04d%02d%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); +} + +/** + * Format iCal time format to either SQL date or SQL date-time format + * + * @param string $datetime icalendar formatted date or date-time + * @return string SQL date or SQL date-time string + * @deprecated Use ZDateHelper::toSqlDateTime() instead + */ +static function toSqlDateTime($datetime = ""){ + if($datetime == "") + return ZDateHelper::toSqlDateTime(); + if(strlen($datetime) > 10) + return sprintf('%04d-%02d-%02d %02d:%02d:%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2), + substr($datetime,11,2),substr($datetime,14,2),substr($datetime,17,2)); + else + return sprintf('%04d-%02d-%02d',substr($datetime,0,4),substr($datetime,5,2),substr($datetime,8,2)); +} + +/** + * Pull timezone data from node and put in array + * + * Returning array contains the following array keys: tzoffsetfrom, tzoffsetto, tzname, dtstart, rrule + * + * @param array $node timezone object + * + * @return array + */ +static function getTZValues($node){ + $tzvalues = array(); + + $tnode = @$node->data['TZOFFSETFROM']; + if($tnode != null){ + $tzvalues["tzoffsetfrom"] = $tnode->getValues(); + } + + $tnode = @$node->data['TZOFFSETTO']; + if($tnode != null){ + $tzvalues["tzoffsetto"] = $tnode->getValues(); + } + + $tnode = @$node->data['TZNAME']; + if($tnode != null){ + $tzvalues["tzname"] = $tnode->getValues(); + } + else + $tzvalues["tzname"] = ""; + + $tnode = @$node->data['DTSTART']; + if($tnode != null){ + $tzvalues["dtstart"] = ZDateHelper::fromiCaltoUnixDateTime($tnode->getValues()); + } + + $tnode = @$node->data['RRULE']; + if($tnode != null){ + $tzvalues["rrule"] = $tnode->getValues(); + //echo "rule: " . $tzvalues["rrule"] . "
\n"; + } + else{ + // no rule specified, let's create one from based on the date + $date = getdate($tzvalues["dtstart"]); + $month = $date["mon"]; + $day = $date["mday"]; + $tzvalues["rrule"] = "FREQ=YEARLY;INTERVAL=1;BYMONTH=$month;BYMONTHDAY=$day"; + } + + return $tzvalues; +} + +/** + * Escape slashes, commas and semicolons in strings + * + * @param string $content + * + * @return string + */ +static function formatContent($content) +{ + $content = str_replace(array('\\' , ',' , ';' ), array('\\\\' , '\\,' , '\\;' ),$content); + return $content; +} + +} + +?> diff --git a/src/lib/includes/index.html b/src/lib/includes/index.html new file mode 100644 index 0000000..53a7f24 --- /dev/null +++ b/src/lib/includes/index.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/lib/includes/recurringdate.php b/src/lib/includes/recurringdate.php new file mode 100644 index 0000000..9f6920f --- /dev/null +++ b/src/lib/includes/recurringdate.php @@ -0,0 +1,796 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Recurring Date Helper Class + * + * Class to expand recurring rule to a list of dates + */ +class ZCRecurringDate { + /** + * rules string + * + * @var string + */ + var $rules = ""; + + /** + * start date in Unix Timestamp format (local timezone) + * + * @var integer + */ + var $startdate = null; + + /** + * repeating frequency type (i.e. "y" for yearly, "m" for monthly) + * + * @var string + */ + var $freq = null; + + /** + * timezone of event (using PHP timezones) + * + * @var string + */ + var $tzid = null; + + /** + * repeat mode ('c': count, 'u': until) + * + * @var string + */ + var $repeatmode=null; + + /** + * repeat until date (in UTC Unix Timestamp format) + * + * @var integer + */ + var $until=null; + + /** + * repeat count when repeat mode is 'c' + * + * @var integer + */ + var $count=0; + + /** + * array of repeat by seconds values + * + * @var array + */ + var $bysecond=array(); + + /** + * array of repeat by minutes values + * + * @var array + */ + var $byminute=array(); + + /** + * array of repeat by hour values + * + * @var array + */ + var $byhour=array(); + + /** + * array of repeat by day values + * + * @var array + */ + var $byday=array(); + + /** + * array of repeat by month day values + * + * @var array + */ + var $bymonthday=array(); + + /** + * array of repeat by month values + * + * @var array + */ + var $bymonth=array(); + + /** + * array of repeat by year values + * + * @var array + */ + var $byyear=array(); + + /** + * array of repeat by setpos values + * + * @var array + */ + var $bysetpos=array(); + + /** + * inteval of repeating event (i.e. every 2 weeks, every 6 months) + * + * @var integer + */ + var $interval = 1; + + /** + * debug level (for testing only) + * + * @var integer + */ + var $debug = 0; + + /** + * error string (future use) + * + * @var string + */ + var $error; + + /** + * array of exception dates in Unix Timestamp format (UTC dates) + * + * @var array + */ + var $exdates=array(); + +/** + * Expand recurring rule to a list of dates + * + * @param string $rules iCalendar rules string + * @param integer $startdate start date in Unix Timestamp format + * @param array $exdates array of exception dates + * @param string $tzid timezone of event (using PHP timezones) + */ + function __construct($rules, $startdate, $exdates = array(),$tzid = "UTC"){ + if(strlen($rules) > 0){ + //move exdates to event timezone for comparing with event date + for($i = 0; $i < count($exdates); $i++) + { + $exdates[$i] = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($exdates[$i]),$tzid)); + } + + $rules=str_replace("\'","",$rules); + $this->rules = $rules; + if($startdate == null){ + // if not specified, use start date of beginning of last year + $tdate=getdate(); + $startdate=mktime(0,0,0,1,1,$tdate["year"] - 1); + } + $this->startdate = $startdate; + $this->tzid = $tzid; + $this->exdates = $exdates; + + $rules=explode(";", $rules); + $ruletype = ""; + foreach($rules as $rule){ + $item=explode("=",$rule); + //echo $item[0] . "=" . $item[1] . "
\n"; + switch($item[0]){ + case "FREQ": + switch($item[1]){ + case "YEARLY": + $this->freq="y"; + break; + case "MONTHLY": + $this->freq="m"; + break; + case "WEEKLY": + $this->freq="w"; + break; + case "DAILY": + $this->freq="d"; + break; + case "HOURLY": + $this->freq="h"; + break; + case "MINUTELY": + $this->freq="i"; + break; + case "SECONDLY": + $this->freq="s"; + break; + } + break; + case "INTERVAL": + $this->interval = $item[1]; + break; + case "BYSECOND": + $this->bysecond = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMINUTE": + $this->byminute = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYHOUR": + $this->byhour = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYDAY": + $this->byday = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMONTHDAY": + $this->bymonthday = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYMONTH": + $this->bymonth = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "BYYEAR": + $this->byyear = explode(",",$item[1]); + $ruletype = $item[0]; + break; + case "COUNT": + $this->count = intval($item[1]); + $this->repeatmode = "c"; + break; + case "BYSETPOS": + $this->bysetpos = explode(",",$item[1]); + break; + case "UNTIL": + $this->until = ZDateHelper::fromiCaltoUnixDateTime($item[1]); + $this->repeatmode = "u"; + break; + } + } + if(count($this->bysetpos) > 0){ + switch($ruletype){ + case "BYYEAR": + $this->byyear = $this->bySetPos($this->byyear,$this->bysetpos); + break; + case "BYMONTH": + $this->bymonth = $this->bySetPos($this->bymonth,$this->bysetpos); + break; + case "BYMONTHDAY": + $this->bymonthday = $this->bySetPos($this->bymonthday,$this->bysetpos); + break; + case "BYDAY": + $this->byday = $this->bySetPos($this->byday,$this->bysetpos); + break; + case "BYHOUR": + $this->byhour = $this->bySetPos($this->byhour,$this->bysetpos); + break; + case "BYMINUTE": + $this->byminute = $this->bySetPos($this->byminute,$this->bysetpos); + break; + case "BYSECOND": + $this->bysecond = $this->bySetPos($this->bysecond,$this->bysetpos); + break; + } + } + } + } + +/** + * bysetpos rule support + * + * @param array $bytype + * @param array $bysetpos + * + * @return array + */ + function bySetPos($bytype, $bysetpos){ + $result = array(); + for($i=0; $i < count($bysetpos); $i++){ + for($j=0; $j < count($bytype); $j++){ + $result[] = $bysetpos[$i] . $bytype[$j]; + } + } + return $result; + } + +/** + * save error + * + * @param string $msg + */ + function setError($msg){ + $this->error = $msg; + } + +/** + * get error message + * + * @return string error message + */ + function getError(){ + return $this->error; + } + +/** + * set debug level (0: none, 1: minimal, 2: more output) + * + * @param integer $level + * + */ + function setDebug($level) + { + $this->debug = $level; + } + +/** + * display debug message + * + * @param integer $level + * @param string $msg + */ + function debug($level, $msg){ + if($this->debug >= $level) + echo $msg . "
\n"; + } + +/** + * Get repeating dates by year + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byYear($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byYear(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byyear) > 0){ + foreach($this->byyear as $year){ + $t = getdate($startdate); + $wdate = mktime($t[hours],$t[minutes],$t[seconds],$t[month],$t[mday],$year); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMonth($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMonth($startdate, $enddate, $rdates, $tzid); + self::debug(1,"byYear() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by month + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMonth($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMonth(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->bymonth) > 0){ + foreach($this->bymonth as $month){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$month,$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMonthDay($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMonthDay($startdate, $enddate, $rdates, $tzid); + self::debug(1,"byMonth() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by month day + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMonthDay($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMonthDay(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); + if(count($this->bymonthday) > 0){ + foreach($this->bymonthday as $day){ + $day = intval($day); + $t = getdate($startdate); + $wdate = mktime($t['hours'],$t['minutes'],$t['seconds'],$t['mon'],$day,$t['year']); + self::debug(2,"mktime(" . $t['hours'] . ", " . $t['minutes'] + . ", " . $t['mon'] . ", " . $day . ", " . $t['year'] . ") returned $wdate"); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byDay($wdate, $enddate, $rdates, $tzid); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) { + self::debug(1,"start date: " . ZDateHelper::toSqlDateTime($startdate)); + $count = $this->byDay($startdate, $enddate, $rdates, $tzid); + } + self::debug(1,"byMonthDay() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by day + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byDay($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byDay(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $days = array( + "SU" => 0, + "MO" => 1, + "TU" => 2, + "WE" => 3, + "TH" => 4, + "FR" => 5, + "SA" => 6); + $idays = array( + 0 => "SU", + 1 => "MO", + 2 => "TU", + 3 => "WE", + 4 => "TH", + 5 => "FR", + 6 => "SA"); + + $count = 0; + if(count($this->byday) > 0){ + if(empty($this->byday[0])) + { + $this->byday[0] = $idays[date("w",$startdate)]; + } + foreach($this->byday as $tday){ + $t = getdate($startdate); + $day = substr($tday,strlen($tday) - 2); + if(strlen($day) < 2) + { + // missing start day, use current date for DOW + $day = $idays[date("w",$startdate)]; + } + if(strlen($tday) > 2) { + $imin = 1; + $imax = 5; // max # of occurances in a month + if(strlen($tday) > 2) + $imin = $imax = substr($tday,0,strlen($tday) - 2); + self::debug(2,"imin: $imin, imax: $imax, tday: $tday, day: $day, daynum: {$days[$day]}"); + for($i = $imin; $i <= $imax; $i++){ + $wdate = ZDateHelper::getDateFromDay($startdate,$i-1,$days[$day],$tzid); + self::debug(2,"getDateFromDay(" . ZDateHelper::toSqlDateTime($startdate) + . ",$i,{$days[$day]}) returned " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byHour($wdate, $enddate, $rdates); + if($count == 0){ + $rdates[] = $wdate; + $count++; + //break; + } + } + } + } + else { + // day of week version + $startdate_dow = date("w",$startdate); + $datedelta = $days[$day] - $startdate_dow; + self::debug(2, "start_dow: $startdate_dow, datedelta: $datedelta"); + if($datedelta >= 0) + { + $wdate = ZDateHelper::addDate($startdate,0,0,0,0,$datedelta,0,$this->tzid); + self::debug(2, "wdate: " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byHour($wdate, $enddate, $rdates); + if($count == 0){ + $rdates[] = $wdate; + $count++; + self::debug(2,"adding date " . ZDateHelper::toSqlDateTime($wdate) ); + } + } + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byHour($startdate, $enddate, $rdates); + self::debug(1,"byDay() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by hour + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byHour($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byHour(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byhour) > 0){ + foreach($this->byhour as $hour){ + $t = getdate($startdate); + $wdate = mktime($hour,$t["minutes"],$t["seconds"],$t["mon"],$t["mday"],$t["year"]); + self::debug(2,"checking date/time " . ZDateHelper::toSqlDateTime($wdate)); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->byMinute($wdate, $enddate, $rdates); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->byMinute($startdate, $enddate, $rdates); + self::debug(1,"byHour() returned " . $count ); + return $count; + } + +/** + * Get repeating dates by minute + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function byMinute($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"byMinute(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->byminute) > 0){ + foreach($this->byminute as $minute){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$minute,$t["seconds"],$t["mon"],$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $count = $this->bySecond($wdate, $enddate, $rdates); + if($count == 0) { + $rdates[] = $wdate; + $count++; + } + } + } + } + else if(!$this->maxDates($rdates)) + $count = $this->bySecond($startdate, $enddate, $rdates); + self::debug(1,"byMinute() returned " . $count ); + return $count; + } +/** + * Get repeating dates by second + * + * @param integer $startdate start date of repeating events, in Unix timestamp format + * @param integer $enddate end date of repeating events, in Unix timestamp format + * @param array $rdates array to contain expanded repeating dates + * @param string $tzid timezone of event (using PHP timezones) + * + * @return integer count of dates + */ + private function bySecond($startdate, $enddate, &$rdates, $tzid="UTC"){ + self::debug(1,"bySecond(" . ZDateHelper::toSqlDateTime($startdate) . "," + . ZDateHelper::toSqlDateTime($enddate) . "," . count($rdates) . " dates)"); + $count = 0; + if(count($this->bysecond) > 0){ + foreach($this->bysecond as $second){ + $t = getdate($startdate); + $wdate = mktime($t["hours"],$t["minutes"],$second,$t["mon"],$t["mday"],$t["year"]); + if($startdate <= $wdate && $wdate < $enddate && !$this->maxDates($rdates)){ + $rdates[] = $wdate; + $count++; + } + } + } + self::debug(1,"bySecond() returned " . $count ); + return $count; + } + +/** + * Determine if the loop has reached the end date + * + * @param array $rdates array of repeating dates + * + * @return boolean + */ + private function maxDates($rdates){ + if($this->repeatmode == "c" && count($rdates) >= $this->count) + return true; // exceeded count + else if(count($rdates) > 0 && $this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ + return true; //past date + } + return false; + } + +/** + * Get array of dates from recurring rule + * + * @param $maxdate integer maximum date to appear in repeating dates in Unix timestamp format + * + * @return array + */ + public function getDates($maxdate = null){ + //$this->debug = 2; + self::debug(1,"getDates()"); + $nextdate = $enddate = $this->startdate; + $rdates = array(); + $done = false; + $eventcount = 0; + $loopcount = 0; + self::debug(2,"freq: " . $this->freq . ", interval: " . $this->interval); + while(!$done){ + self::debug(1,"*** Frequency ({$this->freq}) loop pass $loopcount ***"); + switch($this->freq){ + case "y": + if($eventcount > 0) + { + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,0,$this->interval,$this->tzid); + self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); + if(!empty($this->byday)){ + $t = getdate($nextdate); + $nextdate = gmmktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + } + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,0,1); + break; + case "m": + if($eventcount > 0) + { + + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); + self::debug(2,"addDate() returned " . ZDateHelper::toSqlDateTime($nextdate)); + } + if(count($this->byday) > 0) + { + $t = getdate($nextdate); + if($t["mday"] > 28) + { + //check for short months when using month by day, make sure we do not overshoot the counter and skip a month + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0,$this->tzid); + $t2 = getdate($nextdate); + if($t2["mday"] < $t["mday"]) + { + // oops, skipped a month, backup to previous month + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$t2["mday"] - $t["mday"],0,$this->tzid); + } + } + $t = getdate($nextdate); + $nextdate = mktime($t["hours"],$t["minutes"],$t["seconds"],$t["mon"],1,$t["year"]); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + $enddate=ZDateHelper::addDate($nextdate,0,0,0,$this->interval,0,0); + break; + case "w": + if($eventcount == 0) + $nextdate=$nextdate; + else { + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0,$this->tzid); + if(count($this->byday) > 0){ + $dow = date("w", $nextdate); + // move to beginning of week (Sunday) + $bow = 0; + $diff = $bow - $dow; + if($diff > 0) + $diff = $diff - 7; + $nextdate = ZDateHelper::addDate($nextdate,0,0,0,0,$diff,0); + } + self::debug(2,"nextdate set to $nextdate (". ZDateHelper::toSQLDateTime($nextdate) . ")"); + } + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval*7,0); + break; + case "d": + $nextdate=($eventcount==0?$nextdate: + ZDateHelper::addDate($nextdate,0,0,0,0,$this->interval,0,$this->tzid)); + $enddate=ZDateHelper::addDate($nextdate,0,0,0,0,1,0); + break; + } + + $count = $this->byYear($nextdate,$enddate,$rdates,$this->tzid); + $eventcount += $count; + if($maxdate > 0 && $maxdate < $nextdate) + { + array_pop($rdates); + $done = true; + } + else if($count == 0 && !$this->maxDates($rdates)){ + $rdates[] = $nextdate; + $eventcount++; + } + if($this->maxDates($rdates)) + $done = true; + + $year = date("Y", $nextdate); + if($year > _ZAPCAL_MAXYEAR) + { + $done = true; + } + $loopcount++; + if($loopcount > _ZAPCAL_MAXYEAR){ + $done = true; + throw new Exception("Infinite loop detected in getDates()"); + } + } + if($this->repeatmode == "u" && $rdates[count($rdates) - 1] > $this->until){ + // erase last item + array_pop($rdates); + } + $count1 = count($rdates); + $rdates = array_unique($rdates); + $count2 = count($rdates); + $dups = $count1 - $count2; + $excount = 0; + + foreach($this->exdates as $exdate) + { + if($pos = array_search($exdate,$rdates)) + { + array_splice($rdates,$pos,1); + $excount++; + } + } + self::debug(1,"getDates() returned " . count($rdates) . " dates, removing $dups duplicates, $excount exceptions"); + + + if($this->debug >= 2) + { + self::debug(2,"Recurring Dates:"); + foreach($rdates as $rdate) + { + $d = getdate($rdate); + self::debug(2,ZDateHelper::toSQLDateTime($rdate) . " " . $d["wday"] ); + } + self::debug(2,"Exception Dates:"); + foreach($this->exdates as $exdate) + { + self::debug(2, ZDateHelper::toSQLDateTime($exdate)); + } + //exit; + } + + return $rdates; + } +} diff --git a/src/lib/includes/timezone.php b/src/lib/includes/timezone.php new file mode 100644 index 0000000..5369f05 --- /dev/null +++ b/src/lib/includes/timezone.php @@ -0,0 +1,142 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +// No direct access +defined('_ZAPCAL') or die( 'Restricted access' ); + +/** + * Zap Calendar Time Zone Helper Class + * + * Class to help create timezone section of iCalendar file + * + * @copyright Copyright (C) 2006 - 2016 by Dan Cogliano + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ +class ZCTimeZoneHelper { + + /** + * getTZNode creates VTIMEZONE section in an iCalendar file + * + * @param @startyear int start year of date range + * + * @param @endyear int end year of date range + * + * @param $tzid string PHP timezone, use underscore for multiple words (i.e. "New_York" for "New York") + * + * @param $parentnode object iCalendar object where VTIMEZONE will be created + * + * @return object return VTIMEZONE object + */ + static function getTZNode($startyear, $endyear, $tzid, $parentnode) + { + $tzmins = array(); + $tzmaxs = array(); + if(!array_key_exists($tzid,$tzmins) || $tzmins[$tzid] > $startyear) + { + $tzmins[$tzid] = $startyear; + } + if(!array_key_exists($tzid,$tzmaxs) || $tzmaxs[$tzid] < $endyear) + { + $tzmaxs[$tzid] = $endyear; + } + + foreach(array_keys($tzmins) as $tzid) + { + $tmin = $tzmins[$tzid] - 1; + if(array_key_exists($tzid,$tzmaxs)) + { + $tmax = $tzmaxs[$tzid] + 1; + } + else + { + $tmax = $tzmins[$tzid] + 1; + } + $tstart = gmmktime(0,0,0,1,1,$tmin); + $tend = gmmktime(23,59,59,12,31,$tmax); + $tz = new DateTimeZone($tzid); + $transitions = $tz->getTransitions($tstart,$tend); + $tzobj = new ZCiCalNode("VTIMEZONE", $parentnode, true); + $datanode = new ZCiCalDataNode("TZID:" . str_replace("_"," ",$tzid)); + $tzobj->data[$datanode->getName()] = $datanode; + $count = 0; + $lasttransition = null; + if(count($transitions) == 1) + { + // not enough transitions found, probably UTC + // lets add fake transition at end for those systems that need it (i.e. Outlook) + + $t2 = array(); + $t2["isdst"] = $transitions[0]["isdst"]; + $t2["offset"] = $transitions[0]["offset"]; + $t2["ts"] = $tstart; + $t2["abbr"] = $transitions[0]["abbr"]; + $transitions[] = $t2; + } + foreach($transitions as $transition) + { + $count++; + if($count == 1) + { + $lasttransition = $transition; + continue; // skip first item + } + if($transition["isdst"] == 1) + { + $tobj = new ZCiCalNode("DAYLIGHT", $tzobj); + } + else + { + $tobj = new ZCiCalNode("STANDARD", $tzobj); + } + //$tzobj->data[$tobj->getName()] == $tobj; + + // convert timestamp to local time zone + $ts = ZDateHelper::toUnixDateTime(ZDateHelper::toLocalDateTime(ZDateHelper::toSQLDateTime($transition["ts"]),$tzid)); + $datanode = new ZCiCalDataNode("DTSTART:".ZDateHelper::toICalDateTime($ts)); + $tobj->data[$datanode->getName()] = $datanode; + //echo $ts . " => " . ZDateHelper::toICalDateTime($ts) . "
\n"; exit; + $toffset = $lasttransition["offset"]; + $thours = intval($toffset/60/60); + $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; + if($thours < 0) + { + $offset = sprintf("%03d%02d",$thours,$tmins); + } + else + { + $offset = sprintf("+%02d%02d",$thours,$tmins); + } + $datanode = new ZCiCalDataNode("TZOFFSETFROM:".$offset); + $tobj->data[$datanode->getName()] = $datanode; + + $toffset = $transition["offset"]; + $thours = intval($toffset/60/60); + $tmins = abs($toffset)/60 - intval(abs($toffset)/60/60)*60; + if($thours < 0) + { + $offset = sprintf("%03d%02d",$thours,$tmins); + } + else + { + $offset = sprintf("+%02d%02d",$thours,$tmins); + } + $datanode = new ZCiCalDataNode("TZOFFSETTO:".$offset); + $tobj->data[$datanode->getName()] = $datanode; + + $datanode = new ZCiCalDataNode("TZNAME:".$transition["abbr"]); + $tobj->data[$datanode->getName()] = $datanode; + + $lasttransition = $transition; + } + } + return $tzobj; + } +} diff --git a/src/lib/zapcallib.php b/src/lib/zapcallib.php new file mode 100644 index 0000000..bbad6f0 --- /dev/null +++ b/src/lib/zapcallib.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (C) 2006 - 2017 by Dan Cogliano + * @license GNU GPLv3 + * @link http://icalendar.org/php-library.html + */ + +/** + * used by ZapCalLib + * @var integer + */ +define('_ZAPCAL',1); + +if(!defined('_ZAPCAL_BASE')) +{ + /** + * the base folder of the library + * @var string + */ + define('_ZAPCAL_BASE',__DIR__); +} + +require_once(_ZAPCAL_BASE . '/includes/framework.php'); +
DatumWochentagZeitOrtTitelBeschreibung
" . $event_date . "" . $event_day . "" . $event_time . " Uhr" . $event_location . "" . $event_title . "" . $event_descr; + printURL($event_url); + echo "\t