diff --git a/action/ajax.php b/action/ajax.php --- a/action/ajax.php +++ b/action/ajax.php @@ -1,127 +1,128 @@ hlp =& plugin_load('helper','davcal'); } function register(Doku_Event_Handler $controller) { $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown'); } function handle_ajax_call_unknown(&$event, $param) { if($event->data != 'plugin_davcal') return; $event->preventDefault(); $event->stopPropagation(); global $INPUT; $action = trim($INPUT->post->str('action')); $id = trim($INPUT->post->str('id')); $params = $INPUT->post->arr('params'); $user = $_SERVER['REMOTE_USER']; $write = false; $data = array(); $data['result'] = false; $data['html'] = $this->getLang('unknown_error'); $acl = auth_quickaclcheck($id); if($acl > AUTH_READ) { $write = true; } switch($action) { case 'newEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_added'); $this->hlp->addCalendarEntryToCalendarForPage($id, $user, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'getEvents': $startDate = $INPUT->post->str('start'); $endDate = $INPUT->post->str('end'); $data = $this->hlp->getEventsWithinDateRange($id, $user, $startDate, $endDate); break; case 'editEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_edited'); $this->hlp->editCalendarEntryForPage($id, $user, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'deleteEvent': if($write) { $data['result'] = true; $data['html'] = $this->getLang('event_deleted'); $this->hlp->deleteCalendarEntryForPage($id, $params); } else { $data['result'] = false; $data['html'] = $this->getLang('no_permission'); } break; case 'getSettings': $data['result'] = true; $data['settings'] = $this->hlp->getPersonalSettings($user); + $data['settings']['syncurl'] = $this->hlp->getSyncUrlForPage($id, $user); break; case 'saveSettings': $settings = array(); $settings['weeknumbers'] = $params['weeknumbers']; $settings['timezone'] = $params['timezone']; $settings['workweek'] = $params['workweek']; if($this->hlp->savePersonalSettings($settings, $user)) { $data['result'] = true; $data['html'] = $this->getLang('settings_saved'); } else { $data['result'] = false; $data['html'] = $this->getLang('error_saving'); } break; } // If we are still here, JSON output is requested //json library of DokuWiki require_once DOKU_INC . 'inc/JSON.php'; $json = new JSON(); //set content type header('Content-Type: application/json'); echo $json->encode($data); } } \ No newline at end of file diff --git a/helper.php b/helper.php --- a/helper.php +++ b/helper.php @@ -1,366 +1,468 @@ sqlite =& plugin_load('helper', 'sqlite'); if(!$this->sqlite) { msg('This plugin requires the sqlite plugin. Please install it.'); return; } if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/')) { return; } } public function setCalendarNameForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $calid = $this->getCalendarIdForPage($id); if($calid === false) return $this->createCalendarForPage($name, $description, $id, $userid); - // Update the calendar name here - + $query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ". + "description=".$this->sqlite->quote_string($description)." WHERE ". + "id=".$this->sqlite->quote_string($calid); + $res = $this->sqlite->query($query); + if($res !== false) + return true; + return false; } public function savePersonalSettings($settings, $userid = null) { if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $this->sqlite->query("BEGIN TRANSACTION"); $query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); $this->sqlite->query($query); foreach($settings as $key => $value) { $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (". $this->sqlite->quote_string($userid).", ". $this->sqlite->quote_string($key).", ". $this->sqlite->quote_string($value).")"; $res = $this->sqlite->query($query); if($res === false) return false; } $this->sqlite->query("COMMIT TRANSACTION"); return true; } public function getPersonalSettings($userid = null) { if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; // Some sane default settings $settings = array( 'timezone' => 'local', 'weeknumbers' => '0', 'workweek' => '0' ); $query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); foreach($arr as $row) { $settings[$row['key']] = $row['value']; } return $settings; } public function getCalendarIdForPage($id = null) { if(is_null($id)) { global $ID; $id = $ID; } $query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); if(isset($row['calid'])) return $row['calid']; else return false; } public function getCalendarIdToPageMapping() { $query = "SELECT calid, page FROM pagetocalendarmapping"; $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); return $arr; } public function getCalendarIdsForUser($principalUri) { $user = explode('/', $principalUri); $user = end($user); $mapping = $this->getCalendarIdToPageMapping(); $calids = array(); foreach($mapping as $row) { $id = $row['calid']; $page = $row['page']; $acl = auth_quickaclcheck($page); if($acl >= AUTH_READ) { $write = $acl > AUTH_READ; $calids[$id] = array('readonly' => !$write); } } return $calids; } public function createCalendarForPage($name, $description, $id = null, $userid = null) { if(is_null($id)) { global $ID; $id = $ID; } if(is_null($userid)) $userid = $_SERVER['REMOTE_USER']; $values = array('principals/'.$userid, $name, str_replace(array('/', ' ', ':'), '_', $id), $description, 'VEVENT,VTODO', 0, 1); $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");"; $res = $this->sqlite->query($query); if($res === false) return false; $query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ". "displayname=".$this->sqlite->quote_string($values[1])." AND ". "uri=".$this->sqlite->quote_string($values[2])." AND ". "description=".$this->sqlite->quote_string($values[3]); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); if(isset($row['id'])) { $values = array($id, $row['id']); $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); return ($res !== false); } return false; } public function addCalendarEntryToCalendarForPage($id, $user, $params) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); else $timezone = new \DateTimeZone('UTC'); + $startDate = explode('-', $params['eventfrom']); + $startTime = explode(':', $params['eventfromtime']); + $endDate = explode('-', $params['eventto']); + $endTime = explode(':', $params['eventtotime']); require_once('vendor/autoload.php'); $vcalendar = new \Sabre\VObject\Component\VCalendar(); $event = $vcalendar->add('VEVENT'); + $uuid = \Sabre\VObject\UUIDUtil::getUUID(); + $event->add('UID', $uuid); $event->summary = $params['eventname']; - $dtStart = new \DateTime($params['eventfrom'], $timezone); - $dtEnd = new \DateTime($params['eventto'], $timezone); - $event->DTSTART = $dtStart; - $event->DTEND = $dtEnd; + $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); + $event->add('DTSTAMP', $dtStamp); + $event->add('CREATED', $dtStamp); + $event->add('LAST-MODIFIED', $dtStamp); + $dtStart = new \DateTime(); + $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); + if($params['allday'] != '1') + $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); + $dtStart->setTimezone($timezone); + $dtEnd = new \DateTime(); + $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); + if($params['allday'] != '1') + $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); + $dtEnd->setTimezone($timezone); + // According to the VCal spec, we need to add a whole day here + if($params['allday'] == '1') + $dtEnd->add(new \DateInterval('P1D')); + $dtStartEv = $event->add('DTSTART', $dtStart); + $dtEndEv = $event->add('DTEND', $dtEnd); + if($params['allday'] == '1') + { + $dtStartEv['VALUE'] = 'DATE'; + $dtEndEv['VALUE'] = 'DATE'; + } $calid = $this->getCalendarIdForPage($id); $uri = uniqid('dokuwiki-').'.ics'; $now = new DateTime(); $eventStr = $vcalendar->serialize(); $values = array($calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT', $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(), strlen($eventStr), md5($eventStr), uniqid() ); $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'added'); return true; } return false; } + public function getCalendarSettings($calid) + { + $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); + $res = $this->sqlite->query($query); + $row = $this->sqlite->res2row($res); + return $row; + } + public function getEventsWithinDateRange($id, $user, $startDate, $endDate) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); else $timezone = new \DateTimeZone('UTC'); $data = array(); require_once('vendor/autoload.php'); $calid = $this->getCalendarIdForPage($id); $startTs = new \DateTime($startDate); $endTs = new \DateTime($endDate); - $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid=". + $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE (calendarid=". $this->sqlite->quote_string($calid)." AND firstoccurence > ". $this->sqlite->quote_string($startTs->getTimestamp())." AND firstoccurence < ". - $this->sqlite->quote_string($endTs->getTimestamp()); + $this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=". + $this->sqlite->quote_string($calid)." AND lastoccurence > ". + $this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ". + $this->sqlite->quote_string($endTs->getTimestamp()).")"; $res = $this->sqlite->query($query); $arr = $this->sqlite->res2arr($res); foreach($arr as $row) { if(isset($row['calendardata'])) { + $entry = array(); $vcal = \Sabre\VObject\Reader::read($row['calendardata']); - $start = $vcal->VEVENT->DTSTART->getDateTime(); - $end = $vcal->VEVENT->DTEND->getDateTime(); - $start->setTimezone($timezone); - $end->setTimezone($timezone); - $summary = (string)$vcal->VEVENT->summary; - $data[] = array("title" => $summary, "start" => $start->format(\DateTime::ATOM), - "end" => $end->format(\DateTime::ATOM), - "id" => $row['uid']); + $start = $vcal->VEVENT->DTSTART; + if($start !== null) + { + $dtStart = $start->getDateTime(); + $dtStart->setTimezone($timezone); + $entry['start'] = $dtStart->format(\DateTime::ATOM); + if($start['VALUE'] == 'DATE') + $entry['allDay'] = true; + else + $entry['allDay'] = false; + } + $end = $vcal->VEVENT->DTEND; + if($end !== null) + { + $dtEnd = $end->getDateTime(); + $dtEnd->setTimezone($timezone); + // Subtract the plus one day that was added earlier + //if($entry['allDay'] === true) + // $dtEnd->sub(new \DateInterval('P1D')); + $entry['end'] = $dtEnd->format(\DateTime::ATOM); + } + $entry['title'] = (string)$vcal->VEVENT->summary; + $entry['id'] = $row['uid']; + $data[] = $entry; } } return $data; } public function getEventWithUid($uid) { $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=". $this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); $row = $this->sqlite->res2row($res); return $row; } public function editCalendarEntryForPage($id, $user, $params) { $settings = $this->getPersonalSettings($user); if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') $timezone = new \DateTimeZone($settings['timezone']); else $timezone = new \DateTimeZone('UTC'); + $startDate = explode('-', $params['eventfrom']); + $startTime = explode(':', $params['eventfromtime']); + $endDate = explode('-', $params['eventto']); + $endTime = explode(':', $params['eventtotime']); $uid = $params['uid']; $event = $this->getEventWithUid($uid); require_once('vendor/autoload.php'); if(!isset($event['calendardata'])) return false; $uri = $event['uri']; $calid = $event['calendarid']; $vcal = \Sabre\VObject\Reader::read($event['calendardata']); - $vcal->VEVENT->summary = $params['eventname']; - $dtStart = new \DateTime($params['eventfrom'], $timezone); - $dtEnd = new \DateTime($params['eventto'], $timezone); - $vcal->VEVENT->DTSTART = $dtStart; - $vcal->VEVENT->DTEND = $dtEnd; + $vevent = $vcal->VEVENT; + $vevent->summary = $params['eventname']; + $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); + $vevent->remove('DTSTAMP'); + $vevent->remove('LAST-MODIFIED'); + $vevent->add('DTSTAMP', $dtStamp); + $vevent->add('LAST-MODIFIED', $dtStamp); + $dtStart = new \DateTime(); + $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); + if($params['allday'] != '1') + $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); + $dtStart->setTimezone($timezone); + $dtEnd = new \DateTime(); + $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); + if($params['allday'] != '1') + $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); + $dtEnd->setTimezone($timezone); + // 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); + if($params['allday'] == '1') + { + $dtStartEv['VALUE'] = 'DATE'; + $dtEndEv['VALUE'] = 'DATE'; + } $now = new DateTime(); $eventStr = $vcal->serialize(); $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr). ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()). ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()). ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()). ", size=".strlen($eventStr). ", etag=".$this->sqlite->quote_string(md5($eventStr)). " WHERE uid=".$this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'modified'); return true; } return false; } public function deleteCalendarEntryForPage($id, $params) { $uid = $params['uid']; $event = $this->getEventWithUid($uid); $calid = $event['calendarid']; $uri = $event['uri']; $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid); $res = $this->sqlite->query($query); if($res !== false) { $this->updateSyncTokenLog($calid, $uri, 'deleted'); } return true; } public function getSyncTokenForCalendar($calid) { - $query = "SELECT synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); - $res = $this->sqlite->query($query); - $row = $this->sqlite->res2row($res); + $row = $this->getCalendarSettings($calid); if(isset($row['synctoken'])) return $row['synctoken']; return false; } public function operationNameToOperation($operationName) { switch($operationName) { case 'added': return 1; break; case 'modified': return 2; break; case 'deleted': return 3; break; } return 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 ); $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(". $this->sqlite->quote_and_join($values, ',').")"; $res = $this->sqlite->query($query); if($res === false) return false; $currentToken++; $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=". $this->sqlite->quote_string($calid); $res = $this->sqlite->query($query); return ($res !== false); } + public function getSyncUrlForPage($id, $user = null) + { + if(is_null($user)) + $user = $_SERVER['REMOTE_USER']; + + $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; + } + } diff --git a/lang/en/lang.php b/lang/en/lang.php --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -1,32 +1,33 @@ '); jQuery.post( DOKU_BASE + 'lib/exe/ajax.php', { call: 'plugin_davcal', id: JSINFO.id, action: 'saveSettings', params: postArray }, function(data) { var result = data['result']; var html = data['html']; jQuery('#dw_davcal__ajaxsettings').html(html); if(result === true) { location.reload(); } } ); }; dialogButtons[LANG.plugins.davcal['cancel']] = function () { dw_davcal__modals.hideSettingsDialog(); }; dw_davcal__modals.$settingsDialog = jQuery(document.createElement('div')) .dialog({ autoOpen: false, draggable: true, title: LANG.plugins.davcal['settings'], resizable: true, buttons: dialogButtons, }) .html( '
' + //'' + '' + '' + '' + + '' + '
' + LANG.plugins.davcal['use_lang_tz'] + '
' + LANG.plugins.davcal['timezone'] + '
' + LANG.plugins.davcal['weeknumbers'] + '
' + LANG.plugins.davcal['only_workweek'] + '
' + LANG.plugins.davcal['sync_url'] + '
' + '
' + '
' ) .parent() .attr('id','dw_davcal__settings') .show() .appendTo('.dokuwiki:first'); // attach event handlers jQuery('#dw_davcal__settings .ui-dialog-titlebar-close').click(function(){ dw_davcal__modals.hideSettingsDialog(); }); var $tzdropdown = jQuery('#dw_davcal__settings_timezone'); jQuery('#fullCalendarTimezoneList option').each(function() { jQuery('