diff --git a/all.css b/all.css --- a/all.css +++ b/all.css @@ -1,43 +1,184 @@ @import "fullcalendar-3.2.0/fullcalendar.less"; @import "datetimepicker-2.4.5/jquery.datetimepicker.less"; /* modal windows */ #dw_davcal__edit, #dw_davcal__settings, #dw_davcal__confirm { position: absolute; background-color: #fff; color: #000; z-index: 20; overflow: hidden; } #dw_davcal__edit .ui-dialog-content, #dw_davcal__confirm .ui-dialog-content, #dw_davcal__settings .ui-dialog-content, { padding-left: 0; padding-right: 0; } .dw_davcal__date { width: 90px; } .dw_davcal__time { width: 50px; } .dw_davcal__text { width: 220px; } #dw_davcal__editevent_attachments td { border: 0; } #dokuwiki__pagetools { z-index: 10; } + +.davcalevents +{ + margin:50px auto 0 auto; + width:100%; + align-items:space-around; + max-width:1200px; + display:block; +} +.davcalevents .tile +{ + width:280px; + height:200px; + margin:10px; + + display:inline-block; + background-size:cover; + position:relative; + cursor:pointer; + transition: all 0.4s ease-out; + box-shadow: 0px 35px 77px -17px rgba(0,0,0,0.44); + overflow:hidden; + color:white; + font-family:'Roboto'; + +} +.davcalevents .tile img +{ + height:100%; + width:100%; + position:absolute; + top:0; + left:0; + z-index:0; + transition: all 0.4s ease-out; +} +.davcalevents .tile .text +{ +/* z-index:99; */ + position:absolute; + padding:30px; + height:calc(100% - 60px); +} +.davcalevents .tile h1 +{ + + font-weight:300; + margin:0; + text-shadow: 2px 2px 10px rgba(0,0,0,0.3); +} +.davcalevents .tile h2 +{ + font-weight:100; + margin:5px 0 0 0; + font-style:italic; + /* transform: translateX(200px); */ +} +.davcalevents .tile p +{ + font-weight:300; + margin:20px 0 0 0; + line-height: 25px; +/* opacity:0; */ + transform: translateX(-200px); + transition-delay: 0.2s; +} +.davcalevents .animate-text +{ + opacity:0; + transition: all 0.6s ease-in-out; +} +.davcalevents .tile:hover +{ +/* background-color:#99aeff; */ +box-shadow: 0px 35px 77px -17px rgba(0,0,0,0.64); + transform:scale(1.05); +} +.davcalevents .tile:hover img +{ + opacity: 0.2; +} +.davcalevents .tile:hover .animate-text +{ + transform:translateX(0); + opacity:1; +} +.davcalevents .dots +{ + position:absolute; + bottom:20px; + right:30px; + margin: 0 auto; + width:30px; + height:30px; + color:currentColor; + display:flex; + flex-direction:column; + align-items:center; + justify-content:space-around; + +} + +.davcalevents .dots span +{ + width: 5px; + height:5px; + background-color: currentColor; + border-radius: 50%; + display:block; + opacity:0; + transition: transform 0.4s ease-out, opacity 0.5s ease; + transform: translateY(30px); + +} + +.davcalevents .tile:hover span +{ + opacity:1; + transform:translateY(0px); +} + +.davcalevents .dots span:nth-child(1) +{ + transition-delay: 0.05s; +} +.davcalevents .dots span:nth-child(2) +{ + transition-delay: 0.1s; +} +.davcalevents .dots span:nth-child(3) +{ + transition-delay: 0.15s; +} + + +@media (max-width: 1000px) { + .davcalevents { + flex-direction: column; + width:400px; + } +} \ No newline at end of file diff --git a/helper.php b/helper.php --- a/helper.php +++ b/helper.php @@ -1,1944 +1,1950 @@ sqlite === null) { $this->sqlite = plugin_load('helper', 'sqlite'); if(!$this->sqlite) { dbglog('This plugin requires the sqlite plugin. Please install it.'); msg('This plugin requires the sqlite plugin. Please install it.', -1); return false; } if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/')) { $this->sqlite = null; dbglog('Error initialising the SQLite DB for DAVCal'); return false; } } return $this->sqlite; } /** * Retrieve meta data for a given page * * @param string $id optional The page ID * @return array The metadata */ private function getMeta($id = null) { global $ID; global $INFO; if ($id === null) $id = $ID; if($ID === $id && $INFO['meta']) { $meta = $INFO['meta']; } else { $meta = p_get_metadata($id); } return $meta; } /** * Retrieve the meta data for a given page * * @param string $id optional The page ID * @return array with meta data */ public function getCalendarMetaForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $meta = $this->getMeta($id); if(isset($meta['plugin_davcal'])) return $meta['plugin_davcal']; else return array(); } /** * Check the permission of a user for a given calendar ID * * @param string $id The calendar ID to check * @return int AUTH_* constants */ public function checkCalendarPermission($id) { if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return AUTH_NONE; $connectionId = str_replace('webdav://', '', $id); $settings = $wdc->getConnection($connectionId); if($settings === false) return AUTH_NONE; if($settings['write'] === '1') return AUTH_CREATE; return AUTH_READ; } else { $calid = $this->getCalendarIdForPage($id); // We return AUTH_READ if the calendar does not exist. This makes // davcal happy when there are just included calendars if($calid === false) return AUTH_READ; return auth_quickaclcheck($id); } } /** * Filter calendar pages and return only those where the current * user has at least read permission. * * @param array $calendarPages Array with calendar pages to check * @return array with filtered calendar pages */ public function filterCalendarPagesByUserPermission($calendarPages) { $retList = array(); foreach($calendarPages as $page => $data) { // WebDAV Connections are always readable if(strpos($page, 'webdav://') === 0) { $retList[$page] = $data; } elseif(auth_quickaclcheck($page) >= AUTH_READ) { $retList[$page] = $data; } } return $retList; } /** * Get all calendar pages used by a given page * based on the stored metadata * * @param string $id optional The page id * @return mixed The pages as array or false */ public function getCalendarPagesByMeta($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $meta = $this->getCalendarMetaForPage($id); if(isset($meta['id'])) { // Filter the list of pages by permission $pages = $this->filterCalendarPagesByUserPermission($meta['id']); if(empty($pages)) return false; return $pages; } return false; } /** * Get a list of calendar names/pages/ids/colors * for an array of page ids * * @param array $calendarPages The calendar pages to retrieve * @return array The list */ public function getCalendarMapForIDs($calendarPages) { $data = array(); foreach($calendarPages as $page => $color) { if(strpos($page, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) continue; $connectionId = str_replace('webdav://', '', $page); $settings = $wdc->getConnection($connectionId); if($settings === false) continue; $name = $settings['displayname']; $write = ($settings['write'] === '1'); $calid = $connectionId; $color = '#3a87ad'; } else { $calid = $this->getCalendarIdForPage($page); if($calid !== false) { $settings = $this->getCalendarSettings($calid); $name = $settings['displayname']; $color = $settings['calendarcolor']; $write = (auth_quickaclcheck($page) > AUTH_READ); } else { continue; } } $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid, 'color' => $color, 'write' => $write); } return $data; } /** * Get the saved calendar color for a given page. * * @param string $id optional The page ID * @return mixed The color on success, otherwise false */ public function getCalendarColorForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; return $this->getCalendarColorForCalendar($calid); } /** * Get the saved calendar color for a given calendar ID. * * @param string $id optional The calendar ID * @return mixed The color on success, otherwise false */ public function getCalendarColorForCalendar($calid) { if(isset($this->cachedValues['calendarcolor'][$calid])) return $this->cachedValues['calendarcolor'][$calid]; $row = $this->getCalendarSettings($calid); if(!isset($row['calendarcolor'])) return false; $color = $row['calendarcolor']; $this->cachedValues['calendarcolor'][$calid] = $color; return $color; } /** * Get the user's principal URL for iOS sync * @param string $user the user name * @return the URL to the principal sync */ public function getPrincipalUrlForUser($user) { if(is_null($user)) return false; $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user; return $url; } /** * Set the calendar color for a given page. * * @param string $color The color definition * @param string $id optional The page ID * @return boolean True on success, otherwise false */ public function setCalendarColorForPage($color, $id = null) { if(is_null($id)) { global $ID; $id = $ID; } $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET calendarcolor = ? ". " WHERE id = ?"; $res = $sqlite->query($query, $color, $calid); if($res !== false) { $this->cachedValues['calendarcolor'][$calid] = $color; return true; } return false; } /** * Set the calendar name and description for a given page with a given * page id. * If the calendar doesn't exist, the calendar is created! * * @param string $name The name of the new calendar * @param string $description The description of the new calendar * @param string $id (optional) The ID of the page * @param string $userid The userid of the creating user * * @return boolean True on success, otherwise false. */ public function setCalendarNameForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { $userid = uniqid('davcal-'); } } $calid = $this->getCalendarIdForPage($id); if($calid === false) return $this->createCalendarForPage($name, $description, $id, $userid); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?"; $res = $sqlite->query($query, $name, $description, $calid); if($res !== false) return true; return false; } /** * Update a calendar's displayname * * @param int $calid The calendar's ID * @param string $name The new calendar name * * @return boolean True on success, otherwise false */ public function updateCalendarName($calid, $name) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET displayname = ? WHERE id = ?"; $res = $sqlite->query($query, $calid, $name); if($res !== false) { $this->updateSyncTokenLog($calid, '', 'modified'); return true; } return false; } /** * Update the calendar description * * @param int $calid The calendar's ID * @param string $description The new calendar's description * * @return boolean True on success, otherwise false */ public function updateCalendarDescription($calid, $description) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET description = ? WHERE id = ?"; $res = $sqlite->query($query, $calid, $description); if($res !== false) { $this->updateSyncTokenLog($calid, '', 'modified'); return true; } return false; } /** * Update a calendar's timezone information * * @param int $calid The calendar's ID * @param string $timezone The new timezone to set * * @return boolean True on success, otherwise false */ public function updateCalendarTimezone($calid, $timezone) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET timezone = ? WHERE id = ?"; $res = $sqlite->query($query, $calid, $timezone); if($res !== false) { $this->updateSyncTokenLog($calid, '', 'modified'); return true; } return false; } /** * Save the personal settings to the SQLite database 'calendarsettings'. * * @param array $settings The settings array to store * @param string $userid (optional) The userid to store * * @param boolean True on success, otherwise false */ public function savePersonalSettings($settings, $userid = null) { if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { return false; } } $sqlite = $this->getDB(); if(!$sqlite) return false; $sqlite->query("BEGIN TRANSACTION"); $query = "DELETE FROM calendarsettings WHERE userid = ?"; $sqlite->query($query, $userid); foreach($settings as $key => $value) { $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)"; $res = $sqlite->query($query, $userid, $key, $value); if($res === false) return false; } $sqlite->query("COMMIT TRANSACTION"); $this->cachedValues['settings'][$userid] = $settings; return true; } /** * Retrieve the settings array for a given user id. * Some sane defaults are returned, currently: * * timezone => local * weeknumbers => 0 * workweek => 0 * * @param string $userid (optional) The user id to retrieve * * @return array The settings array */ public function getPersonalSettings($userid = null) { // Some sane default settings $settings = array( 'timezone' => $this->getConf('timezone'), 'weeknumbers' => $this->getConf('weeknumbers'), 'workweek' => $this->getConf('workweek'), 'monday' => $this->getConf('monday'), 'timeformat' => $this->getConf('timeformat') ); if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { return $settings; } } $sqlite = $this->getDB(); if(!$sqlite) return false; if(isset($this->cachedValues['settings'][$userid])) return $this->cachedValues['settings'][$userid]; $query = "SELECT key, value FROM calendarsettings WHERE userid = ?"; $res = $sqlite->query($query, $userid); $arr = $sqlite->res2arr($res); foreach($arr as $row) { $settings[$row['key']] = $row['value']; } $this->cachedValues['settings'][$userid] = $settings; return $settings; } /** * Retrieve the calendar ID based on a page ID from the SQLite table * 'pagetocalendarmapping'. * * @param string $id (optional) The page ID to retrieve the corresponding calendar * * @return mixed the ID on success, otherwise false */ public function getCalendarIdForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } if(isset($this->cachedValues['calid'][$id])) return $this->cachedValues['calid'][$id]; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?"; $res = $sqlite->query($query, $id); $row = $sqlite->res2row($res); if(isset($row['calid'])) { $calid = $row['calid']; $this->cachedValues['calid'] = $calid; return $calid; } return false; } /** * Retrieve the complete calendar id to page mapping. * This is necessary to be able to retrieve a list of * calendars for a given user and check the access rights. * * @return array The mapping array */ public function getCalendarIdToPageMapping() { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calid, page FROM pagetocalendarmapping"; $res = $sqlite->query($query); $arr = $sqlite->res2arr($res); return $arr; } /** * Retrieve all calendar IDs a given user has access to. * The user is specified by the principalUri, so the * user name is actually split from the URI component. * * Access rights are checked against DokuWiki's ACL * and applied accordingly. * * @param string $principalUri The principal URI to work on * * @return array An associative array of calendar IDs */ public function getCalendarIdsForUser($principalUri) { global $auth; $user = explode('/', $principalUri); $user = end($user); $mapping = $this->getCalendarIdToPageMapping(); $calids = array(); $ud = $auth->getUserData($user); $groups = $ud['grps']; foreach($mapping as $row) { $id = $row['calid']; $enabled = $this->getCalendarStatus($id); if($enabled == false) continue; $page = $row['page']; $acl = auth_aclcheck($page, $user, $groups); if($acl >= AUTH_READ) { $write = $acl > AUTH_READ; $calids[$id] = array('readonly' => !$write); } } return $calids; } /** * Create a new calendar for a given page ID and set name and description * accordingly. Also update the pagetocalendarmapping table on success. * * @param string $name The calendar's name * @param string $description The calendar's description * @param string $id (optional) The page ID to work on * @param string $userid (optional) The user ID that created the calendar * * @return boolean True on success, otherwise false */ public function createCalendarForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { $userid = uniqid('davcal-'); } } $values = array('principals/'.$userid, $name, str_replace(array('/', ' ', ':'), '_', $id), $description, 'VEVENT,VTODO', 0, 1); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ". "VALUES (?, ?, ?, ?, ?, ?, ?)"; $res = $sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]); if($res === false) return false; // Get the new calendar ID $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ". "uri = ? AND description = ?"; $res = $sqlite->query($query, $values[0], $values[1], $values[2], $values[3]); $row = $sqlite->res2row($res); // Update the pagetocalendarmapping table with the new calendar ID if(isset($row['id'])) { $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)"; $res = $sqlite->query($query, $id, $row['id']); return ($res !== false); } return false; } /** * Add a new calendar entry to the given calendar. Calendar data is * specified as ICS file, thus it needs to be parsed first. * * This is mainly needed for the sync support. * * @param int $calid The calendar's ID * @param string $uri The new object URI * @param string $ics The ICS file * * @return mixed The etag. */ public function addCalendarEntryToCalendarByICS($calid, $uri, $ics) { $extraData = $this->getDenormalizedData($ics); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)"; $res = $sqlite->query($query, $calid, $uri, $ics, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid']); // If successfully, update the sync token database if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'added'); } return $extraData['etag']; } /** * Edit a calendar entry by providing a new ICS file. This is mainly * needed for the sync support. * * @param int $calid The calendar's IS * @param string $uri The object's URI to modify * @param string $ics The new object's ICS file */ public function editCalendarEntryToCalendarByICS($calid, $uri, $ics) { $extraData = $this->getDenormalizedData($ics); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?"; $res = $sqlite->query($query, $ics, time(), $extraData['etag'], $extraData['size'], $extraData['componentType'], $extraData['firstOccurence'], $extraData['lastOccurence'], $extraData['uid'], $calid, $uri ); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'modified'); } return $extraData['etag']; } /** * Add a new iCal entry for a given page, i.e. a given calendar. * * The parameter array needs to contain * detectedtz => The timezone as detected by the browser * currenttz => The timezone in use by the calendar * eventfrom => The event's start date * eventfromtime => The event's start time * eventto => The event's end date * eventtotime => The event's end time * eventname => The event's name * eventdescription => The event's description * * @param string $id The page ID to work on * @param string $user The user who created the calendar * @param string $params A parameter array with values to create * * @return boolean True on success, otherwise false */ public function addCalendarEntryToCalendarForPage($id, $user, $params) { if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') $timezone = new \DateTimeZone($params['currenttz']); elseif($params['currenttz'] === 'local') $timezone = new \DateTimeZone($params['detectedtz']); else $timezone = new \DateTimeZone('UTC'); // Retrieve dates from settings $startDate = explode('-', $params['eventfrom']); $startTime = explode(':', $params['eventfromtime']); $endDate = explode('-', $params['eventto']); $endTime = explode(':', $params['eventtotime']); // Load SabreDAV require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); $vcalendar = new \Sabre\VObject\Component\VCalendar(); // Add VCalendar, UID and Event Name $event = $vcalendar->add('VEVENT'); $uuid = \Sabre\VObject\UUIDUtil::getUUID(); $event->add('UID', $uuid); $event->summary = $params['eventname']; // Add a description if requested $description = $params['eventdescription']; if($description !== '') $event->add('DESCRIPTION', $description); // Add a location if requested $location = $params['eventlocation']; if($location !== '') $event->add('LOCATION', $location); // Add attachments $attachments = $params['attachments']; if(!is_null($attachments)) foreach($attachments as $attachment) $event->add('ATTACH', $attachment); // Create a timestamp for last modified, created and dtstamp values in UTC $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); $event->add('DTSTAMP', $dtStamp); $event->add('CREATED', $dtStamp); $event->add('LAST-MODIFIED', $dtStamp); // Adjust the start date, based on the given timezone information $dtStart = new \DateTime(); $dtStart->setTimezone($timezone); $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); // Only add the time values if it's not an allday event if($params['allday'] != '1') $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); // Adjust the end date, based on the given timezone information $dtEnd = new \DateTime(); $dtEnd->setTimezone($timezone); $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); // Only add the time values if it's not an allday event if($params['allday'] != '1') $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); // According to the VCal spec, we need to add a whole day here if($params['allday'] == '1') $dtEnd->add(new \DateInterval('P1D')); // Really add Start and End events $dtStartEv = $event->add('DTSTART', $dtStart); $dtEndEv = $event->add('DTEND', $dtEnd); // Adjust the DATE format for allday events if($params['allday'] == '1') { $dtStartEv['VALUE'] = 'DATE'; $dtEndEv['VALUE'] = 'DATE'; } $eventStr = $vcalendar->serialize(); if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return false; $connectionId = str_replace('webdav://', '', $id); return $wdc->addCalendarEntry($connectionId, $eventStr); } else { // Actually add the values to the database $calid = $this->getCalendarIdForPage($id); $uri = $uri = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics'; $now = new \DateTime(); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; $res = $sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT', $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(), strlen($eventStr), md5($eventStr), $uuid); // If successfully, update the sync token database if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'added'); return true; } } return false; } /** * Retrieve the calendar settings of a given calendar id * * @param string $calid The calendar ID * * @return array The calendar settings array */ public function getCalendarSettings($calid) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT id, principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken, disabled FROM calendars WHERE id= ? "; $res = $sqlite->query($query, $calid); $row = $sqlite->res2row($res); return $row; } /** * Retrieve the calendar status of a given calendar id * * @param string $calid The calendar ID * @return boolean True if calendar is enabled, otherwise false */ public function getCalendarStatus($calid) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT disabled FROM calendars WHERE id = ?"; $res = $sqlite->query($query, $calid); $row = $sqlite->res2row($res); if($row['disabled'] == 1) return false; else return true; } /** * Disable a calendar for a given page * * @param string $id The page ID * * @return boolean true on success, otherwise false */ public function disableCalendarForPage($id) { $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET disabled = 1 WHERE id = ?"; $res = $sqlite->query($query, $calid); if($res !== false) return true; return false; } /** * Enable a calendar for a given page * * @param string $id The page ID * * @return boolean true on success, otherwise false */ public function enableCalendarForPage($id) { $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "UPDATE calendars SET disabled = 0 WHERE id = ?"; $res = $sqlite->query($query, $calid); if($res !== false) return true; return false; } /** * Retrieve all events that are within a given date range, * based on the timezone setting. * * There is also support for retrieving recurring events, * using Sabre's VObject Iterator. Recurring events are represented * as individual calendar entries with the same UID. * * @param string $id The page ID to work with * @param string $user The user ID to work with * @param string $startDate The start date as a string * @param string $endDate The end date as a string * @param string $color (optional) The calendar's color + * @param array $additional (optional) Parse additional fields * * @return array An array containing the calendar entries. */ - public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null) + public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null, $additional = array()) { if($timezone !== '' && $timezone !== 'local') $timezone = new \DateTimeZone($timezone); else $timezone = new \DateTimeZone('UTC'); $data = array(); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?"; $startTs = null; $endTs = null; if($startDate !== null) { $startTs = new \DateTime($startDate); $query .= " AND lastoccurence > ".$sqlite->quote_string($startTs->getTimestamp()); } if($endDate !== null) { $endTs = new \DateTime($endDate); $query .= " AND firstoccurence < ".$sqlite->quote_string($endTs->getTimestamp()); } // Load SabreDAV require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return $data; $connectionId = str_replace('webdav://', '', $id); $arr = $wdc->getCalendarEntries($connectionId, $startDate, $endDate); } else { $calid = $this->getCalendarIdForPage($id); if(is_null($color)) $color = $this->getCalendarColorForCalendar($calid); $enabled = $this->getCalendarStatus($calid); if($enabled === false) return $data; // Retrieve matching calendar objects $res = $sqlite->query($query, $calid); $arr = $sqlite->res2arr($res); } // Parse individual calendar entries foreach($arr as $row) { if(isset($row['calendardata'])) { $entry = array(); $vcal = \Sabre\VObject\Reader::read($row['calendardata']); $recurrence = $vcal->VEVENT->RRULE; // If it is a recurring event, pass it through Sabre's EventIterator if($recurrence != null) { $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT)); $rEvents->rewind(); while($rEvents->valid()) { $event = $rEvents->getEventObject(); // If we are after the given time range, exit if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp())) break; // If we are before the given time range, continue if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp())) { $rEvents->next(); continue; } // If we are within the given time range, parse the event - $data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true); + $data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true, $additional); $rEvents->next(); } } else - $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color); + $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color, false, $additional); } } return $data; } /** * Helper function that parses the iCal data of a VEVENT to a calendar entry. * * @param \Sabre\VObject\VEvent $event The event to parse * @param \DateTimeZone $timezone The timezone object * @param string $uid The entry's UID * @param boolean $recurring (optional) Set to true to define a recurring event + * @param array $additional (optional) Parse additional fields * * @return array The parse calendar entry */ - private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false) + private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false, $additional = array()) { $entry = array(); $start = $event->DTSTART; // Parse only if the start date/time is present if($start !== null) { $dtStart = $start->getDateTime(); $dtStart->setTimezone($timezone); // moment.js doesn't like times be given even if // allDay is set to true // This should fix T23 if($start['VALUE'] == 'DATE') { $entry['allDay'] = true; $entry['start'] = $dtStart->format("Y-m-d"); } else { $entry['allDay'] = false; $entry['start'] = $dtStart->format(\DateTime::ATOM); } } $end = $event->DTEND; // Parse only if the end date/time is present if($end !== null) { $dtEnd = $end->getDateTime(); $dtEnd->setTimezone($timezone); if($end['VALUE'] == 'DATE') $entry['end'] = $dtEnd->format("Y-m-d"); else $entry['end'] = $dtEnd->format(\DateTime::ATOM); } $description = $event->DESCRIPTION; if($description !== null) $entry['description'] = (string)$description; else $entry['description'] = ''; $attachments = $event->ATTACH; if($attachments !== null) { $entry['attachments'] = array(); foreach($attachments as $attachment) $entry['attachments'][] = (string)$attachment; } + foreach($additional as $prop) + { + $entry[$prop] = (string)$event->{$prop}; + } $entry['title'] = (string)$event->summary; $entry['location'] = (string)$event->location; $entry['id'] = $uid; $entry['page'] = $page; $entry['color'] = $color; $entry['recurring'] = $recurring; return $entry; } /** * Retrieve an event by its UID * * @param string $uid The event's UID * * @return mixed The table row with the given event */ public function getEventWithUid($uid) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid = ?"; $res = $sqlite->query($query, $uid); $row = $sqlite->res2row($res); return $row; } /** * Retrieve information of a calendar's object, not including the actual * calendar data! This is mainly needed for the sync support. * * @param int $calid The calendar ID * * @return mixed The result */ public function getCalendarObjects($calid) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM calendarobjects WHERE calendarid = ?"; $res = $sqlite->query($query, $calid); $arr = $sqlite->res2arr($res); return $arr; } /** * Retrieve a single calendar object by calendar ID and URI * * @param int $calid The calendar's ID * @param string $uri The object's URI * * @return mixed The result */ public function getCalendarObjectByUri($calid, $uri) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri = ?"; $res = $sqlite->query($query, $calid, $uri); $row = $sqlite->res2row($res); return $row; } /** * Retrieve several calendar objects by specifying an array of URIs. * This is mainly neede for sync. * * @param int $calid The calendar's ID * @param array $uris An array of URIs * * @return mixed The result */ public function getMultipleCalendarObjectsByUri($calid, $uris) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri IN ("; // Inserting a whole bunch of question marks $query .= implode(',', array_fill(0, count($uris), '?')); $query .= ')'; $vals = array_merge(array($calid), $uris); $res = $sqlite->query($query, $vals); $arr = $sqlite->res2arr($res); return $arr; } /** * Retrieve all calendar events for a given calendar ID * * @param string $calid The calendar's ID * * @return array An array containing all calendar data */ public function getAllCalendarEvents($calid) { $enabled = $this->getCalendarStatus($calid); if($enabled === false) return false; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?"; $res = $sqlite->query($query, $calid); $arr = $sqlite->res2arr($res); return $arr; } /** * Edit a calendar entry for a page, given by its parameters. * The params array has the same format as @see addCalendarEntryForPage * * @param string $id The page's ID to work on * @param string $user The user's ID to work on * @param array $params The parameter array for the edited calendar event * * @return boolean True on success, otherwise false */ public function editCalendarEntryForPage($id, $user, $params) { if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') $timezone = new \DateTimeZone($params['currenttz']); elseif($params['currenttz'] === 'local') $timezone = new \DateTimeZone($params['detectedtz']); else $timezone = new \DateTimeZone('UTC'); // Parse dates $startDate = explode('-', $params['eventfrom']); $startTime = explode(':', $params['eventfromtime']); $endDate = explode('-', $params['eventto']); $endTime = explode(':', $params['eventtotime']); // Retrieve the existing event based on the UID $uid = $params['uid']; if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return false; $event = $wdc->getCalendarEntryByUid($uid); } else { $event = $this->getEventWithUid($uid); } // Load SabreDAV require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); if(!isset($event['calendardata'])) return false; $uri = $event['uri']; $calid = $event['calendarid']; // Parse the existing event $vcal = \Sabre\VObject\Reader::read($event['calendardata']); $vevent = $vcal->VEVENT; // Set the new event values $vevent->summary = $params['eventname']; $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); $description = $params['eventdescription']; $location = $params['eventlocation']; // Remove existing timestamps to overwrite them $vevent->remove('DESCRIPTION'); $vevent->remove('DTSTAMP'); $vevent->remove('LAST-MODIFIED'); $vevent->remove('ATTACH'); $vevent->remove('LOCATION'); // Add new time stamps, description and location $vevent->add('DTSTAMP', $dtStamp); $vevent->add('LAST-MODIFIED', $dtStamp); if($description !== '') $vevent->add('DESCRIPTION', $description); if($location !== '') $vevent->add('LOCATION', $location); // Add attachments $attachments = $params['attachments']; if(!is_null($attachments)) foreach($attachments as $attachment) $vevent->add('ATTACH', $attachment); // Setup DTSTART $dtStart = new \DateTime(); $dtStart->setTimezone($timezone); $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); if($params['allday'] != '1') $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); // Setup DTEND $dtEnd = new \DateTime(); $dtEnd->setTimezone($timezone); $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); if($params['allday'] != '1') $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); // According to the VCal spec, we need to add a whole day here if($params['allday'] == '1') $dtEnd->add(new \DateInterval('P1D')); $vevent->remove('DTSTART'); $vevent->remove('DTEND'); $dtStartEv = $vevent->add('DTSTART', $dtStart); $dtEndEv = $vevent->add('DTEND', $dtEnd); // Remove the time for allday events if($params['allday'] == '1') { $dtStartEv['VALUE'] = 'DATE'; $dtEndEv['VALUE'] = 'DATE'; } $eventStr = $vcal->serialize(); if(strpos($id, 'webdav://') === 0) { $connectionId = str_replace('webdav://', '', $id); return $wdc->editCalendarEntry($connectionId, $uid, $eventStr); } else { $sqlite = $this->getDB(); if(!$sqlite) return false; $now = new DateTime(); // Actually write to the database $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ". "firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?"; $res = $sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(), $dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'modified'); return true; } } return false; } /** * Delete an event from a calendar by calendar ID and URI * * @param int $calid The calendar's ID * @param string $uri The object's URI * * @return true */ public function deleteCalendarEntryForCalendarByUri($calid, $uri) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?"; $res = $sqlite->query($query, $calid, $uri); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'deleted'); } return true; } /** * Delete a calendar entry for a given page. Actually, the event is removed * based on the entry's UID, so that page ID is no used. * * @param string $id The page's ID (unused) * @param array $params The parameter array to work with * * @return boolean True */ public function deleteCalendarEntryForPage($id, $params) { $uid = $params['uid']; if(strpos($id, 'webdav://') === 0) { $wdc =& plugin_load('helper', 'webdavclient'); if(is_null($wdc)) return false; $connectionId = str_replace('webdav://', '', $id); $result = $wdc->deleteCalendarEntry($connectionId, $uid); return $result; } $sqlite = $this->getDB(); if(!$sqlite) return false; $event = $this->getEventWithUid($uid); $calid = $event['calendarid']; $uri = $event['uri']; $query = "DELETE FROM calendarobjects WHERE uid = ?"; $res = $sqlite->query($query, $uid); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'deleted'); } return true; } /** * Retrieve the current sync token for a calendar * * @param string $calid The calendar id * * @return mixed The synctoken or false */ public function getSyncTokenForCalendar($calid) { $row = $this->getCalendarSettings($calid); if(isset($row['synctoken'])) return $row['synctoken']; return false; } /** * Helper function to convert the operation name to * an operation code as stored in the database * * @param string $operationName The operation name * * @return mixed The operation code or false */ public function operationNameToOperation($operationName) { switch($operationName) { case 'added': return 1; break; case 'modified': return 2; break; case 'deleted': return 3; break; } return false; } /** * Update the sync token log based on the calendar id and the * operation that was performed. * * @param string $calid The calendar ID that was modified * @param string $uri The calendar URI that was modified * @param string $operation The operation that was performed * * @return boolean True on success, otherwise false */ private function updateSyncTokenLog($calid, $uri, $operation) { $currentToken = $this->getSyncTokenForCalendar($calid); $operationCode = $this->operationNameToOperation($operation); if(($operationCode === false) || ($currentToken === false)) return false; $values = array($uri, $currentToken, $calid, $operationCode ); $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)"; $res = $sqlite->query($query, $uri, $currentToken, $calid, $operationCode); if($res === false) return false; $currentToken++; $query = "UPDATE calendars SET synctoken = ? WHERE id = ?"; $res = $sqlite->query($query, $currentToken, $calid); return ($res !== false); } /** * Return the sync URL for a given Page, i.e. a calendar * * @param string $id The page's ID * @param string $user (optional) The user's ID * * @return mixed The sync url or false */ public function getSyncUrlForPage($id, $user = null) { if(is_null($userid)) { if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) { $userid = $_SERVER['REMOTE_USER']; } else { return false; } } $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; $calsettings = $this->getCalendarSettings($calid); if(!isset($calsettings['uri'])) return false; $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; return $syncurl; } /** * Return the private calendar's URL for a given page * * @param string $id the page ID * * @return mixed The private URL or false */ public function getPrivateURLForPage($id) { $calid = $this->getCalendarIdForPage($id); if($calid === false) return false; return $this->getPrivateURLForCalendar($calid); } /** * Return the private calendar's URL for a given calendar ID * * @param string $calid The calendar's ID * * @return mixed The private URL or false */ public function getPrivateURLForCalendar($calid) { if(isset($this->cachedValues['privateurl'][$calid])) return $this->cachedValues['privateurl'][$calid]; $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?"; $res = $sqlite->query($query, $calid); $row = $sqlite->res2row($res); if(!isset($row['url'])) { $url = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics'; $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)"; $res = $sqlite->query($query, $url, $calid); if($res === false) return false; } else { $url = $row['url']; } $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url; $this->cachedValues['privateurl'][$calid] = $url; return $url; } /** * Retrieve the calendar ID for a given private calendar URL * * @param string $url The private URL * * @return mixed The calendar ID or false */ public function getCalendarForPrivateURL($url) { $sqlite = $this->getDB(); if(!$sqlite) return false; $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?"; $res = $sqlite->query($query, $url); $row = $sqlite->res2row($res); if(!isset($row['calid'])) return false; return $row['calid']; } /** * Return a given calendar as ICS feed, i.e. all events in one ICS file. * * @param string $calid The calendar ID to retrieve * * @return mixed The calendar events as string or false */ public function getCalendarAsICSFeed($calid) { $calSettings = $this->getCalendarSettings($calid); if($calSettings === false) return false; $events = $this->getAllCalendarEvents($calid); if($events === false) return false; // Load SabreDAV require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:"; $out .= $calSettings['displayname']."\r\n"; foreach($events as $event) { $vcal = \Sabre\VObject\Reader::read($event['calendardata']); $evt = $vcal->VEVENT; $out .= $evt->serialize(); } $out .= "END:VCALENDAR\r\n"; return $out; } /** * Retrieve a configuration option for the plugin * * @param string $key The key to query * @return mixed The option set, null if not found */ public function getConfig($key) { return $this->getConf($key); } /** * Parses some information from calendar objects, used for optimized * calendar-queries. Taken nearly unmodified from Sabre's PDO backend * * Returns an array with the following keys: * * etag - An md5 checksum of the object without the quotes. * * size - Size of the object in bytes * * componentType - VEVENT, VTODO or VJOURNAL * * firstOccurence * * lastOccurence * * uid - value of the UID property * * @param string $calendarData * @return array */ protected function getDenormalizedData($calendarData) { require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); $vObject = \Sabre\VObject\Reader::read($calendarData); $componentType = null; $component = null; $firstOccurence = null; $lastOccurence = null; $uid = null; foreach ($vObject->getComponents() as $component) { if ($component->name !== 'VTIMEZONE') { $componentType = $component->name; $uid = (string)$component->UID; break; } } if (!$componentType) { return false; } if ($componentType === 'VEVENT') { $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->getValue())); $lastOccurence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); $lastOccurence = $endDate->getTimeStamp(); } else { $lastOccurence = $firstOccurence; } } else { $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime('2038-01-01'); if ($it->isInfinite()) { $lastOccurence = $maxDate->getTimeStamp(); } else { $end = $it->getDtEnd(); while ($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurence = $end->getTimeStamp(); } } } return array( 'etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => $firstOccurence, 'lastOccurence' => $lastOccurence, 'uid' => $uid, ); } /** * Query a calendar by ID and taking several filters into account. * This is heavily based on Sabre's PDO backend. * * @param int $calendarId The calendar's ID * @param array $filters The filter array to apply * * @return mixed The result */ public function calendarQuery($calendarId, $filters) { dbglog('davcal::helper::calendarQuery'); $componentType = null; $requirePostFilter = true; $timeRange = null; $sqlite = $this->getDB(); if(!$sqlite) return false; // if no filters were specified, we don't need to filter after a query if (!$filters['prop-filters'] && !$filters['comp-filters']) { $requirePostFilter = false; } // Figuring out if there's a component filter if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { $componentType = $filters['comp-filters'][0]['name']; // Checking if we need post-filters if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { $requirePostFilter = false; } // There was a time-range filter if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) { $timeRange = $filters['comp-filters'][0]['time-range']; // If start time OR the end time is not specified, we can do a // 100% accurate mysql query. if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { $requirePostFilter = false; } } } if ($requirePostFilter) { $query = "SELECT uri, calendardata FROM calendarobjects WHERE calendarid = ?"; } else { $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?"; } $values = array( $calendarId ); if ($componentType) { $query .= " AND componenttype = ?"; $values[] = $componentType; } if ($timeRange && $timeRange['start']) { $query .= " AND lastoccurence > ?"; $values[] = $timeRange['start']->getTimeStamp(); } if ($timeRange && $timeRange['end']) { $query .= " AND firstoccurence < ?"; $values[] = $timeRange['end']->getTimeStamp(); } $res = $sqlite->query($query, $values); $arr = $sqlite->res2arr($res); $result = array(); foreach($arr as $row) { if ($requirePostFilter) { if (!$this->validateFilterForObject($row, $filters)) { continue; } } $result[] = $row['uri']; } return $result; } /** * This method validates if a filter (as passed to calendarQuery) matches * the given object. Taken from Sabre's PDO backend * * @param array $object * @param array $filters * @return bool */ protected function validateFilterForObject($object, $filters) { require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); // Unfortunately, setting the 'calendardata' here is optional. If // it was excluded, we actually need another call to get this as // well. if (!isset($object['calendardata'])) { $object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']); } $vObject = \Sabre\VObject\Reader::read($object['calendardata']); $validator = new \Sabre\CalDAV\CalendarQueryValidator(); $res = $validator->validate($vObject, $filters); return $res; } /** * Retrieve changes for a given calendar based on the given syncToken. * * @param int $calid The calendar's ID * @param int $syncToken The supplied sync token * @param int $syncLevel The sync level * @param int $limit The limit of changes * * @return array The result */ public function getChangesForCalendar($calid, $syncToken, $syncLevel, $limit = null) { // Current synctoken $currentToken = $this->getSyncTokenForCalendar($calid); if ($currentToken === false) return null; $result = array( 'syncToken' => $currentToken, 'added' => array(), 'modified' => array(), 'deleted' => array(), ); $sqlite = $this->getDB(); if(!$sqlite) return false; if ($syncToken) { $query = "SELECT uri, operation FROM calendarchanges WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken"; if ($limit > 0) $query .= " LIMIT " . (int)$limit; // Fetching all changes $res = $sqlite->query($query, $syncToken, $currentToken, $calid); if($res === false) return null; $arr = $sqlite->res2arr($res); $changes = array(); // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. foreach($arr as $row) { $changes[$row['uri']] = $row['operation']; } foreach ($changes as $uri => $operation) { switch ($operation) { case 1 : $result['added'][] = $uri; break; case 2 : $result['modified'][] = $uri; break; case 3 : $result['deleted'][] = $uri; break; } } } else { // No synctoken supplied, this is the initial sync. $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?"; $res = $sqlite->query($query); $arr = $sqlite->res2arr($res); $result['added'] = $arr; } return $result; } } diff --git a/lang/de-informal/lang.php b/lang/de-informal/lang.php --- a/lang/de-informal/lang.php +++ b/lang/de-informal/lang.php @@ -1,64 +1,65 @@ */ $lang['created_by_davcal'] = 'Erstellt von DAVCal für DokuWiki'; $lang['unknown_error'] = 'Unbekannter Fehler'; $lang['event_added'] = 'Eintrag hinzugefügt'; $lang['event_edited'] = 'Eintrag bearbeitet'; $lang['event_deleted'] = 'Eintrag entfernt'; $lang['no_permission'] = 'Du hast nicht die erforderlichen Rechte, um den Eintrag zu bearbeiten'; $lang['settings'] = 'Einstellungen/Sync'; $lang['settings_saved'] = 'Einstellungen erfolgreich gespeichert'; $lang['error_saving'] = 'Fehler beim Speichern der Einstellungen.'; $lang['local_time'] = 'Lokale Zeit (Browser-basierend)'; $lang['from'] = 'Von'; $lang['to'] = 'Bis'; $lang['at'] = 'Am'; $lang['description'] = 'Beschreibung'; $lang['title'] = 'Titel'; $lang['error_timezone_not_in_list'] = 'Die angegebene Zeitzone wird von PHP nicht unterstützt'; $lang['error_option_error'] = 'In den Optionen wurden ein Fehler festgestellt!'; $lang['this_calendar_uses_timezone'] = 'Dieser Kalender wird in der Zeitzone %s angezeigt.'; +$lang['costs'] = 'Kosten'; $lang['js']['create_new_event'] = 'Neuen Eintrag anlegen'; $lang['js']['cancel'] = 'Abbrechen'; $lang['js']['create'] = 'Anlegen'; $lang['js']['save'] = 'Speichern'; $lang['js']['settings'] = 'Einstellungen'; $lang['js']['edit'] = 'Bearbeiten'; $lang['js']['delete'] = 'Löschen'; $lang['js']['edit_event'] = 'Eintrag bearbeiten'; $lang['js']['allday'] = 'Ganzer Tag'; $lang['js']['title'] = 'Titel'; $lang['js']['location'] = 'Ort'; $lang['js']['from'] = 'Von'; $lang['js']['to'] = 'Bis'; $lang['js']['yes'] = 'Ja'; $lang['js']['confirmation'] = 'Bestätigung'; $lang['js']['ok'] = 'OK'; $lang['js']['info'] = 'Info'; $lang['js']['really_delete_this_event'] = 'Diesen Eintrag wirklich löschen?'; $lang['js']['timezone'] = 'Zeitzone'; $lang['js']['weeknumbers'] = 'Wochennummern'; $lang['js']['use_lang_tz'] = 'Zeitzone der Spracheinstellung verwenden'; $lang['js']['only_workweek'] = 'Zeige nur die Arbeitswoche'; $lang['js']['sync_url'] = 'CalDAV URL'; $lang['js']['error_retrieving_data'] = 'Beim Abfragen der Daten vom Kalenderserver ist ein Fehler aufgetreten.'; $lang['js']['start_date_invalid'] = 'Beginnzeit/-datum ist ungültig'; $lang['js']['end_date_invalid'] = 'Endzeit/-datum ist ungültig'; $lang['js']['end_date_before_start_date'] = 'Endzeit/-datum liegt vor Startzeit/-datum'; $lang['js']['end_date_is_same_as_start_date'] = 'Endzeit/-datum ist gleich wie Startzeit/-datum'; $lang['js']['description'] = 'Beschreibung'; $lang['js']['private_url'] = 'Private URL'; $lang['js']['recurring_cant_edit'] = 'Das Bearbeiten wiederkehrender Einträge wird derzeit nicht unterstützt'; $lang['js']['no_permission'] = 'Du hast nicht die erforderlichen Rechte, diesen Kalender zu bearbeiten'; $lang['js']['calendar'] = 'Kalender'; $lang['js']['start_monday'] = 'Die Woche beginnt am Montag'; $lang['js']['nothing_to_show'] = 'Nichts anzuzeigen'; $lang['js']['add_attachment'] = 'Link hinzufügen'; $lang['js']['attachments'] = 'Angehängte Links'; $lang['js']['language_specific'] = 'Sprachabhängig'; $lang['js']['sync_ical'] = 'iCal Sync URL'; $lang['js']['timeformat'] = 'Zeitformat'; diff --git a/lang/de/lang.php b/lang/de/lang.php --- a/lang/de/lang.php +++ b/lang/de/lang.php @@ -1,64 +1,65 @@ */ $lang['created_by_davcal'] = 'Erstellt von DAVCal für DokuWiki'; $lang['unknown_error'] = 'Unbekannter Fehler'; $lang['event_added'] = 'Eintrag hinzugefügt'; $lang['event_edited'] = 'Eintrag bearbeitet'; $lang['event_deleted'] = 'Eintrag entfernt'; $lang['no_permission'] = 'Sie haben nicht die erforderlichen Rechte, um den Eintrag zu bearbeiten'; $lang['settings'] = 'Einstellungen/Sync'; $lang['settings_saved'] = 'Einstellungen erfolgreich gespeichert'; $lang['error_saving'] = 'Fehler beim Speichern der Einstellungen.'; $lang['local_time'] = 'Lokale Zeit (Browser-basierend)'; $lang['from'] = 'Von'; $lang['to'] = 'Bis'; $lang['at'] = 'Am'; $lang['description'] = 'Beschreibung'; $lang['title'] = 'Titel'; $lang['error_timezone_not_in_list'] = 'Die angegebene Zeitzone wird von PHP nicht unterstützt'; $lang['error_option_error'] = 'In den Optionen wurden ein Fehler festgestellt!'; $lang['this_calendar_uses_timezone'] = 'Dieser Kalender wird in der Zeitzone %s angezeigt.'; +$lang['costs'] = 'Kosten'; $lang['js']['create_new_event'] = 'Neuen Eintrag anlegen'; $lang['js']['cancel'] = 'Abbrechen'; $lang['js']['create'] = 'Anlegen'; $lang['js']['save'] = 'Speichern'; $lang['js']['settings'] = 'Einstellungen'; $lang['js']['edit'] = 'Bearbeiten'; $lang['js']['delete'] = 'Löschen'; $lang['js']['edit_event'] = 'Eintrag bearbeiten'; $lang['js']['allday'] = 'Ganzer Tag'; $lang['js']['title'] = 'Titel'; $lang['js']['location'] = 'Ort'; $lang['js']['from'] = 'Von'; $lang['js']['to'] = 'Bis'; $lang['js']['yes'] = 'Ja'; $lang['js']['confirmation'] = 'Bestätigung'; $lang['js']['ok'] = 'OK'; $lang['js']['info'] = 'Info'; $lang['js']['really_delete_this_event'] = 'Diesen Eintrag wirklich löschen?'; $lang['js']['timezone'] = 'Zeitzone'; $lang['js']['weeknumbers'] = 'Wochennummern'; $lang['js']['use_lang_tz'] = 'Zeitzone der Spracheinstellung verwenden'; $lang['js']['only_workweek'] = 'Zeige nur die Arbeitswoche'; $lang['js']['sync_url'] = 'CalDAV URL'; $lang['js']['error_retrieving_data'] = 'Beim Abfragen der Daten vom Kalenderserver ist ein Fehler aufgetreten.'; $lang['js']['start_date_invalid'] = 'Beginnzeit/-datum ist ungültig'; $lang['js']['end_date_invalid'] = 'Endzeit/-datum ist ungültig'; $lang['js']['end_date_before_start_date'] = 'Endzeit/-datum liegt vor Startzeit/-datum'; $lang['js']['end_date_is_same_as_start_date'] = 'Endzeit/-datum ist gleich wie Startzeit/-datum'; $lang['js']['description'] = 'Beschreibung'; $lang['js']['private_url'] = 'Private URL'; $lang['js']['recurring_cant_edit'] = 'Das Bearbeiten wiederkehrender Einträge wird derzeit nicht unterstützt'; $lang['js']['no_permission'] = 'Sie haben nicht die erforderlichen Rechte, diesen Kalender zu bearbeiten'; $lang['js']['calendar'] = 'Kalender'; $lang['js']['start_monday'] = 'Die Woche beginnt am Montag'; $lang['js']['nothing_to_show'] = 'Nichts anzuzeigen'; $lang['js']['add_attachment'] = 'Link hinzufügen'; $lang['js']['attachments'] = 'Angehängte Links'; $lang['js']['language_specific'] = 'Sprachabhängig'; $lang['js']['sync_ical'] = 'iCal Sync URL'; $lang['js']['timeformat'] = 'Zeitformat'; diff --git a/lang/en/lang.php b/lang/en/lang.php --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -1,64 +1,65 @@ */ $lang['created_by_davcal'] = 'Created by DAVCal for DokuWiki'; $lang['unknown_error'] = 'Unknown Error'; $lang['event_added'] = 'Event added'; $lang['event_edited'] = 'Event edited'; $lang['event_deleted'] = 'Event deleted'; $lang['no_permission'] = 'You don\'t have permission to modify this calendar'; $lang['settings'] = 'Settings/Sync'; $lang['settings_saved'] = 'Settings saved successfully'; $lang['error_saving'] = 'Error saving settings.'; $lang['local_time'] = 'Local time (Browser based)'; $lang['from'] = 'From'; $lang['to'] = 'To'; $lang['at'] = 'At'; $lang['description'] = 'Description'; $lang['title'] = 'Title'; $lang['error_timezone_not_in_list'] = 'The desired timezone is not supported by PHP!'; $lang['error_option_error'] = 'You have an error in the option list'; $lang['this_calendar_uses_timezone'] = 'This calendar is shown in the timezone %s.'; +$lang['costs'] = 'Costs'; $lang['js']['create_new_event'] = 'Create new Event'; $lang['js']['cancel'] = 'Cancel'; $lang['js']['create'] = 'Create'; $lang['js']['save'] = 'Save'; $lang['js']['settings'] = 'Settings'; $lang['js']['edit'] = 'Edit'; $lang['js']['delete'] = 'Delete'; $lang['js']['edit_event'] = 'Edit Event'; $lang['js']['allday'] = 'All Day Event'; $lang['js']['title'] = 'Title'; $lang['js']['location'] = 'Location'; $lang['js']['from'] = 'From'; $lang['js']['to'] = 'To'; $lang['js']['yes'] = 'Yes'; $lang['js']['confirmation'] = 'Confirmation'; $lang['js']['ok'] = 'OK'; $lang['js']['info'] = 'Info'; $lang['js']['really_delete_this_event'] = 'Really delete this event?'; $lang['js']['timezone'] = 'Timezone'; $lang['js']['weeknumbers'] = 'Week Numbers'; $lang['js']['use_lang_tz'] = 'Use Timezone from language setting'; $lang['js']['only_workweek'] = 'Show only Work Week'; $lang['js']['sync_url'] = 'CalDAV URL'; $lang['js']['error_retrieving_data'] = 'There was an error retrieving data from the calendar server.'; $lang['js']['start_date_invalid'] = 'Start date/time is invalid.'; $lang['js']['end_date_invalid'] = 'End date/time is invalid.'; $lang['js']['end_date_before_start_date'] = 'End date/time is before start date/time.'; $lang['js']['end_date_is_same_as_start_date'] = 'End date/time is equal to start date/time.'; $lang['js']['description'] = 'Description'; $lang['js']['private_url'] = 'Private URL'; $lang['js']['recurring_cant_edit'] = 'Editing recurring events is currently not supported'; $lang['js']['no_permission'] = 'You don\'t have permission to modify this calendar'; $lang['js']['calendar'] = 'Calendar'; $lang['js']['start_monday'] = 'Week starts on Monday'; $lang['js']['nothing_to_show'] = 'Nothing to show'; $lang['js']['add_attachment'] = 'Add link'; $lang['js']['attachments'] = 'Attached links'; $lang['js']['language_specific'] = 'Language specific'; $lang['js']['sync_ical'] = 'iCal Sync URL'; $lang['js']['timeformat'] = 'Time Format'; diff --git a/syntax/events.php b/syntax/events.php new file mode 100644 --- /dev/null +++ b/syntax/events.php @@ -0,0 +1,344 @@ + + */ + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); +require_once(DOKU_PLUGIN.'syntax.php'); + +class syntax_plugin_davcal_events extends DokuWiki_Syntax_Plugin { + + protected $hlp = null; + + // Load the helper plugin + public function syntax_plugin_davcal_events() { + $this->hlp =& plugin_load('helper', 'davcal'); + } + + + /** + * What kind of syntax are we? + */ + function getType(){ + return 'substition'; + } + + /** + * What about paragraphs? + */ + function getPType(){ + return 'normal'; + } + + /** + * Where to sort in? + */ + function getSort(){ + return 165; + } + + /** + * Connect pattern to lexer + */ + function connectTo($mode) { + $this->Lexer->addSpecialPattern('\{\{davcalevents>[^}]*\}\}',$mode,'plugin_davcal_events'); + } + + /** + * Handle the match + */ + function handle($match, $state, $pos, Doku_Handler $handler){ + global $ID; + $data = array('id' => array(), + 'startdate' => 'today', + 'numdays' => 30, + 'startisend' => false, + 'dateformat' => 'Y-m-d H:i', + 'alldayformat' => 'Y-m-d', + 'onlystart' => false, + 'sort' => 'desc', + 'timezone' => 'local', + 'timeformat' => null, + ); + + $lastid = $ID; + $options = trim(substr($match,15,-2)); + $options = explode(',', $options); + + foreach($options as $option) + { + list($key, $val) = explode('=', $option); + $key = strtolower(trim($key)); + $val = trim($val); + switch($key) + { + case 'id': + $lastid = $val; + if(!in_array($val, $data['id'])) + $data['id'][$val] = null; + break; + case 'color': + $data['id'][$lastid] = $val; + break; + case 'onlystart': + if(($val === 'on') || ($val === 'true')) + $data['onlystart'] = true; + break; + case 'startisend': + if(($val === 'on') || ($val === 'true')) + $data['startisend'] = true; + break; + case 'timezone': + $tzlist = \DateTimeZone::listIdentifiers(DateTimeZone::ALL); + if(in_array($val, $tzlist) || $val === 'no') + $data['timezone'] = $val; + else + msg($this->getLang('error_timezone_not_in_list'), -1); + break; + default: + $data[$key] = $val; + } + } + + // Handle the default case when the user didn't enter a different ID + if(empty($data['id'])) + { + $data['id'] = array($ID => '#3a87ad'); + } + + // Fix up the colors, if no color information is given + foreach($data['id'] as $id => $color) + { + if(is_null($color)) + { + // If this is the current calendar or a WebDAV calendar, use the + // default color + if(($id === $ID) || (strpos($id, 'webdav://') === 0)) + { + $data['id'][$id] = '#3a87ad'; + } + // Otherwise, retrieve the color information from the calendar settings + else + { + $calid = $this->hlp->getCalendarIdForPage($ID); + $settings = $this->hlp->getCalendarSettings($calid); + $color = $settings['calendarcolor']; + $data['id'][$id] = $color; + } + } + } + + return $data; + } + + private static function sort_events_asc($a, $b) + { + $from1 = new \DateTime($a['start']); + $from2 = new \DateTime($b['start']); + return $from2 < $from1; + } + + private static function sort_events_desc($a, $b) + { + $from1 = new \DateTime($a['start']); + $from2 = new \DateTime($b['start']); + return $from1 < $from2; + } + + /** + * Create output + */ + function render($format, Doku_Renderer $R, $data) { + if($format == 'metadata') + { + $R->meta['plugin_davcal']['events'] = true; + return true; + } + if(($format != 'xhtml') && ($format != 'odt')) return false; + global $ID; + + $events = array(); + $from = $data['startdate']; + $toStr = null; + + // Handle the various options to 'startDate' + if($from === 'today') + { + $from = new \DateTime(); + } + elseif(strpos($from, 'today-') === 0) + { + $days = intval(str_replace('today-', '', $from)); + $from = new \DateTime(); + $from->sub(new \DateInterval('P'.$days.'D')); + } + elseif(strpos($from, 'today+') === 0) + { + $days = intval(str_replace('today+', '', $from)); + $from = new \DateTime(); + $from->add(new \DateInterval('P'.$days.'D')); + } + else + { + $from = new \DateTime($from); + } + + // Handle the option 'startisend' + if($data['startisend'] === true) + { + if($data['numdays'] > 0) + { + $to = clone $from; + $to->sub(new \DateInterval('P'.$data['numdays'].'D')); + $fromStr = $to->format('Y-m-d'); + } + else + { + $fromStr = null; + } + $toStr = $from->format('Y-m-d'); + } + else + { + if($data['numdays'] > 0) + { + $to = clone $from; + $to->add(new \DateInterval('P'.$data['numdays'].'D')); + $toStr = $to->format('Y-m-d'); + } + else + { + $toStr = null; + } + $fromStr = $from->format('Y-m-d'); + } + + // Support for timezone + $timezone = $data['timezone']; + + // Filter events by user permissions + $userEvents = $this->hlp->filterCalendarPagesByUserPermission($data['id']); + + // Fetch the events + foreach($userEvents as $calPage => $color) + { + $events = array_merge($events, $this->hlp->getEventsWithinDateRange($calPage, + $user, $fromStr, $toStr, $timezone, null, + array('URL', 'X-COST'))); + + } + // Sort the events + if($data['sort'] === 'desc') + usort($events, array("syntax_plugin_davcal_events", "sort_events_desc")); + else + usort($events, array("syntax_plugin_davcal_events", "sort_events_asc")); + + $R->doc .= '
'; + + + foreach($events as $event) + { + $from = new \DateTime($event['start']); + + $color = $data['id'][$event['page']]; + $R->doc .= '
'; + $R->doc .= '
'; + if($timezone !== 'local') + { + $from->setTimezone(new \DateTimeZone($timezone)); + $to->setTimezone(new \DateTimeZone($timezone)); + } + if($event['allDay'] === true) + $R->doc .= '

'.$from->format($data['alldayformat']).'

'; + else + $R->doc .= '

'.$from->format($data['dateformat']).'

'; + if(!is_null($data['timeformat']) && $event['allDay'] != true) + { + $R->doc .= '

'.$from->format($data['timeformat']).'

'; + } + $R->doc .= $event['title'].'
'; + $R->doc .= ''.$this->getLang('costs').': '.$event['X-COST'].''; + $R->doc .= '
'; + $R->doc .= ''; + $R->doc .= ''; + $R->doc .= ''; + $R->doc .= '
'; + $R->doc .= '
'; + $R->doc .= '
'; + } + + $R->doc .= '
'; + + /* + // Create tabular output + $R->table_open(); + $R->tablethead_open(); + $R->tableheader_open(); + $R->doc .= $data['onlystart'] ? $this->getLang('at') : $this->getLang('from'); + $R->tableheader_close(); + if(!$data['onlystart']) + { + $R->tableheader_open(); + $R->doc .= $this->getLang('to'); + $R->tableheader_close(); + } + $R->tableheader_open(); + $R->doc .= $this->getLang('title'); + $R->tableheader_close(); + $R->tableheader_open(); + $R->doc .= $this->getLang('description'); + $R->tableheader_close(); + $R->tablethead_close(); + foreach($events as $event) + { + $R->tablerow_open(); + $R->tablecell_open(); + $from = new \DateTime($event['start']); + if($timezone !== 'local') + { + $from->setTimezone(new \DateTimeZone($timezone)); + $to->setTimezone(new \DateTimeZone($timezone)); + } + if($event['allDay'] === true) + $R->doc .= $from->format($data['alldayformat']); + else + $R->doc .= $from->format($data['dateformat']); + $R->tablecell_close(); + if(!$data['onlystart']) + { + $to = new \DateTime($event['end']); + // Fixup all day events, which have one day in excess + if($event['allDay'] === true) + { + $to->sub(new \DateInterval('P1D')); + } + $R->tablecell_open(); + if($event['allDay'] === true) + $R->doc .= $to->format($data['alldayformat']); + else + $R->doc .= $to->format($data['dateformat']); + $R->tablecell_close(); + } + $R->tablecell_open(); + $R->doc .= $event['title']; + $R->tablecell_close(); + $R->tablecell_open(); + $R->doc .= $event['description']; + $R->tablecell_close(); + $R->tablerow_close(); + } + $R->table_close(); + */ + } + + + +} + +// vim:ts=4:sw=4:et:enc=utf-8: diff --git a/syntax/table.php b/syntax/table.php --- a/syntax/table.php +++ b/syntax/table.php @@ -1,279 +1,277 @@ */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); class syntax_plugin_davcal_table extends DokuWiki_Syntax_Plugin { protected $hlp = null; // Load the helper plugin public function syntax_plugin_davcal_table() { $this->hlp =& plugin_load('helper', 'davcal'); } /** * What kind of syntax are we? */ function getType(){ return 'substition'; } /** * What about paragraphs? */ function getPType(){ return 'normal'; } /** * Where to sort in? */ function getSort(){ return 165; } /** * Connect pattern to lexer */ function connectTo($mode) { $this->Lexer->addSpecialPattern('\{\{davcaltable>[^}]*\}\}',$mode,'plugin_davcal_table'); } /** * Handle the match */ function handle($match, $state, $pos, Doku_Handler $handler){ global $ID; $options = trim(substr($match,14,-2)); $options = explode(',', $options); $data = array('id' => array(), 'startdate' => 'today', 'numdays' => 30, 'startisend' => false, 'dateformat' => 'Y-m-d H:i', 'alldayformat' => 'Y-m-d', 'onlystart' => false, 'sort' => 'desc', 'timezone' => 'local' ); - $lastid = $ID; foreach($options as $option) { list($key, $val) = explode('=', $option); $key = strtolower(trim($key)); $val = trim($val); switch($key) { case 'id': - $lastid = $val; if(!in_array($val, $data['id'])) $data['id'][$val] = '#3a87ad'; break; case 'onlystart': if(($val === 'on') || ($val === 'true')) $data['onlystart'] = true; break; case 'startisend': if(($val === 'on') || ($val === 'true')) $data['startisend'] = true; break; case 'timezone': $tzlist = \DateTimeZone::listIdentifiers(DateTimeZone::ALL); if(in_array($val, $tzlist) || $val === 'no') $data['timezone'] = $val; else msg($this->getLang('error_timezone_not_in_list'), -1); break; default: $data[$key] = $val; } } // Handle the default case when the user didn't enter a different ID if(empty($data['id'])) { $data['id'] = array($ID => '#3a87ad'); } return $data; } private static function sort_events_asc($a, $b) { $from1 = new \DateTime($a['start']); $from2 = new \DateTime($b['start']); return $from2 < $from1; } private static function sort_events_desc($a, $b) { $from1 = new \DateTime($a['start']); $from2 = new \DateTime($b['start']); return $from1 < $from2; } /** * Create output */ function render($format, Doku_Renderer $R, $data) { if($format == 'metadata') { $R->meta['plugin_davcal']['table'] = true; return true; } if(($format != 'xhtml') && ($format != 'odt')) return false; global $ID; $events = array(); $from = $data['startdate']; $toStr = null; // Handle the various options to 'startDate' if($from === 'today') { $from = new \DateTime(); } elseif(strpos($from, 'today-') === 0) { $days = intval(str_replace('today-', '', $from)); $from = new \DateTime(); $from->sub(new \DateInterval('P'.$days.'D')); } elseif(strpos($from, 'today+') === 0) { $days = intval(str_replace('today+', '', $from)); $from = new \DateTime(); $from->add(new \DateInterval('P'.$days.'D')); } else { $from = new \DateTime($from); } // Handle the option 'startisend' if($data['startisend'] === true) { if($data['numdays'] > 0) { $to = clone $from; $to->sub(new \DateInterval('P'.$data['numdays'].'D')); $fromStr = $to->format('Y-m-d'); } else { $fromStr = null; } $toStr = $from->format('Y-m-d'); } else { if($data['numdays'] > 0) { $to = clone $from; $to->add(new \DateInterval('P'.$data['numdays'].'D')); $toStr = $to->format('Y-m-d'); } else { $toStr = null; } $fromStr = $from->format('Y-m-d'); } // Support for timezone $timezone = $data['timezone']; // Filter events by user permissions $userEvents = $this->hlp->filterCalendarPagesByUserPermission($data['id']); // Fetch the events foreach($userEvents as $calPage => $color) { $events = array_merge($events, $this->hlp->getEventsWithinDateRange($calPage, $user, $fromStr, $toStr, $timezone)); } // Sort the events if($data['sort'] === 'desc') usort($events, array("syntax_plugin_davcal_table", "sort_events_desc")); else usort($events, array("syntax_plugin_davcal_table", "sort_events_asc")); // Create tabular output $R->table_open(); $R->tablethead_open(); $R->tableheader_open(); $R->doc .= $data['onlystart'] ? $this->getLang('at') : $this->getLang('from'); $R->tableheader_close(); if(!$data['onlystart']) { $R->tableheader_open(); $R->doc .= $this->getLang('to'); $R->tableheader_close(); } $R->tableheader_open(); $R->doc .= $this->getLang('title'); $R->tableheader_close(); $R->tableheader_open(); $R->doc .= $this->getLang('description'); $R->tableheader_close(); $R->tablethead_close(); foreach($events as $event) { $R->tablerow_open(); $R->tablecell_open(); $from = new \DateTime($event['start']); if($timezone !== 'local') { $from->setTimezone(new \DateTimeZone($timezone)); $to->setTimezone(new \DateTimeZone($timezone)); } if($event['allDay'] === true) $R->doc .= $from->format($data['alldayformat']); else $R->doc .= $from->format($data['dateformat']); $R->tablecell_close(); if(!$data['onlystart']) { $to = new \DateTime($event['end']); // Fixup all day events, which have one day in excess if($event['allDay'] === true) { $to->sub(new \DateInterval('P1D')); } $R->tablecell_open(); if($event['allDay'] === true) $R->doc .= $to->format($data['alldayformat']); else $R->doc .= $to->format($data['dateformat']); $R->tablecell_close(); } $R->tablecell_open(); $R->doc .= $event['title']; $R->tablecell_close(); $R->tablecell_open(); $R->doc .= $event['description']; $R->tablecell_close(); $R->tablerow_close(); } $R->table_close(); } } // vim:ts=4:sw=4:et:enc=utf-8: