1<?php 2/** 3 * Helper Class for the tagrevisions plugin 4 * This helper does the actual work. 5 * 6 * Configurable in DokuWiki's configuration 7 */ 8 9// must be run within Dokuwiki 10if(!defined('DOKU_INC')) die(); 11 12class helper_plugin_davcal extends DokuWiki_Plugin { 13 14 protected $sqlite = null; 15 16 /** 17 * Constructor to load the configuration 18 */ 19 public function helper_plugin_davcal() { 20 $this->sqlite =& plugin_load('helper', 'sqlite'); 21 if(!$this->sqlite) 22 { 23 msg('This plugin requires the sqlite plugin. Please install it.'); 24 return; 25 } 26 27 if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/')) 28 { 29 return; 30 } 31 } 32 33 public function setCalendarNameForPage($name, $description, $id = null, $userid = null) 34 { 35 if(is_null($id)) 36 { 37 global $ID; 38 $id = $ID; 39 } 40 if(is_null($userid)) 41 $userid = $_SERVER['REMOTE_USER']; 42 $calid = $this->getCalendarIdForPage($id); 43 if($calid === false) 44 return $this->createCalendarForPage($name, $description, $id, $userid); 45 46 $query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ". 47 "description=".$this->sqlite->quote_string($description)." WHERE ". 48 "id=".$this->sqlite->quote_string($calid); 49 $res = $this->sqlite->query($query); 50 if($res !== false) 51 return true; 52 return false; 53 } 54 55 public function savePersonalSettings($settings, $userid = null) 56 { 57 if(is_null($userid)) 58 $userid = $_SERVER['REMOTE_USER']; 59 $this->sqlite->query("BEGIN TRANSACTION"); 60 61 $query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); 62 $this->sqlite->query($query); 63 64 foreach($settings as $key => $value) 65 { 66 $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (". 67 $this->sqlite->quote_string($userid).", ". 68 $this->sqlite->quote_string($key).", ". 69 $this->sqlite->quote_string($value).")"; 70 $res = $this->sqlite->query($query); 71 if($res === false) 72 return false; 73 } 74 $this->sqlite->query("COMMIT TRANSACTION"); 75 return true; 76 } 77 78 public function getPersonalSettings($userid = null) 79 { 80 if(is_null($userid)) 81 $userid = $_SERVER['REMOTE_USER']; 82 // Some sane default settings 83 $settings = array( 84 'timezone' => 'local', 85 'weeknumbers' => '0', 86 'workweek' => '0' 87 ); 88 $query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); 89 $res = $this->sqlite->query($query); 90 $arr = $this->sqlite->res2arr($res); 91 foreach($arr as $row) 92 { 93 $settings[$row['key']] = $row['value']; 94 } 95 return $settings; 96 } 97 98 public function getCalendarIdForPage($id = null) 99 { 100 if(is_null($id)) 101 { 102 global $ID; 103 $id = $ID; 104 } 105 106 $query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id); 107 $res = $this->sqlite->query($query); 108 $row = $this->sqlite->res2row($res); 109 if(isset($row['calid'])) 110 return $row['calid']; 111 else 112 return false; 113 } 114 115 public function getCalendarIdToPageMapping() 116 { 117 $query = "SELECT calid, page FROM pagetocalendarmapping"; 118 $res = $this->sqlite->query($query); 119 $arr = $this->sqlite->res2arr($res); 120 return $arr; 121 } 122 123 public function getCalendarIdsForUser($principalUri) 124 { 125 $user = explode('/', $principalUri); 126 $user = end($user); 127 $mapping = $this->getCalendarIdToPageMapping(); 128 $calids = array(); 129 foreach($mapping as $row) 130 { 131 $id = $row['calid']; 132 $page = $row['page']; 133 $acl = auth_quickaclcheck($page); 134 if($acl >= AUTH_READ) 135 { 136 $write = $acl > AUTH_READ; 137 $calids[$id] = array('readonly' => !$write); 138 } 139 } 140 return $calids; 141 } 142 143 public function createCalendarForPage($name, $description, $id = null, $userid = null) 144 { 145 if(is_null($id)) 146 { 147 global $ID; 148 $id = $ID; 149 } 150 if(is_null($userid)) 151 $userid = $_SERVER['REMOTE_USER']; 152 $values = array('principals/'.$userid, 153 $name, 154 str_replace(array('/', ' ', ':'), '_', $id), 155 $description, 156 'VEVENT,VTODO', 157 0, 158 1); 159 $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");"; 160 $res = $this->sqlite->query($query); 161 if($res === false) 162 return false; 163 $query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ". 164 "displayname=".$this->sqlite->quote_string($values[1])." AND ". 165 "uri=".$this->sqlite->quote_string($values[2])." AND ". 166 "description=".$this->sqlite->quote_string($values[3]); 167 $res = $this->sqlite->query($query); 168 $row = $this->sqlite->res2row($res); 169 if(isset($row['id'])) 170 { 171 $values = array($id, $row['id']); 172 $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; 173 $res = $this->sqlite->query($query); 174 return ($res !== false); 175 } 176 177 return false; 178 } 179 180 public function addCalendarEntryToCalendarForPage($id, $user, $params) 181 { 182 $settings = $this->getPersonalSettings($user); 183 if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') 184 $timezone = new \DateTimeZone($settings['timezone']); 185 else 186 $timezone = new \DateTimeZone('UTC'); 187 $startDate = explode('-', $params['eventfrom']); 188 $startTime = explode(':', $params['eventfromtime']); 189 $endDate = explode('-', $params['eventto']); 190 $endTime = explode(':', $params['eventtotime']); 191 require_once('vendor/autoload.php'); 192 $vcalendar = new \Sabre\VObject\Component\VCalendar(); 193 $event = $vcalendar->add('VEVENT'); 194 $uuid = \Sabre\VObject\UUIDUtil::getUUID(); 195 $event->add('UID', $uuid); 196 $event->summary = $params['eventname']; 197 $description = $params['eventdescription']; 198 if($description !== '') 199 $event->add('DESCRIPTION', $description); 200 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 201 $event->add('DTSTAMP', $dtStamp); 202 $event->add('CREATED', $dtStamp); 203 $event->add('LAST-MODIFIED', $dtStamp); 204 $dtStart = new \DateTime(); 205 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 206 if($params['allday'] != '1') 207 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 208 $dtStart->setTimezone($timezone); 209 $dtEnd = new \DateTime(); 210 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 211 if($params['allday'] != '1') 212 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 213 $dtEnd->setTimezone($timezone); 214 // According to the VCal spec, we need to add a whole day here 215 if($params['allday'] == '1') 216 $dtEnd->add(new \DateInterval('P1D')); 217 $dtStartEv = $event->add('DTSTART', $dtStart); 218 $dtEndEv = $event->add('DTEND', $dtEnd); 219 if($params['allday'] == '1') 220 { 221 $dtStartEv['VALUE'] = 'DATE'; 222 $dtEndEv['VALUE'] = 'DATE'; 223 } 224 $calid = $this->getCalendarIdForPage($id); 225 $uri = uniqid('dokuwiki-').'.ics'; 226 $now = new DateTime(); 227 $eventStr = $vcalendar->serialize(); 228 229 $values = array($calid, 230 $uri, 231 $eventStr, 232 $now->getTimestamp(), 233 'VEVENT', 234 $event->DTSTART->getDateTime()->getTimeStamp(), 235 $event->DTEND->getDateTime()->getTimeStamp(), 236 strlen($eventStr), 237 md5($eventStr), 238 uniqid() 239 ); 240 241 $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; 242 $res = $this->sqlite->query($query); 243 if($res !== false) 244 { 245 $this->updateSyncTokenLog($calid, $uri, 'added'); 246 return true; 247 } 248 return false; 249 } 250 251 public function getCalendarSettings($calid) 252 { 253 $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); 254 $res = $this->sqlite->query($query); 255 $row = $this->sqlite->res2row($res); 256 return $row; 257 } 258 259 public function getEventsWithinDateRange($id, $user, $startDate, $endDate) 260 { 261 $settings = $this->getPersonalSettings($user); 262 if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') 263 $timezone = new \DateTimeZone($settings['timezone']); 264 else 265 $timezone = new \DateTimeZone('UTC'); 266 $data = array(); 267 require_once('vendor/autoload.php'); 268 $calid = $this->getCalendarIdForPage($id); 269 $startTs = new \DateTime($startDate); 270 $endTs = new \DateTime($endDate); 271 $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE (calendarid=". 272 $this->sqlite->quote_string($calid)." AND firstoccurence > ". 273 $this->sqlite->quote_string($startTs->getTimestamp())." AND firstoccurence < ". 274 $this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=". 275 $this->sqlite->quote_string($calid)." AND lastoccurence > ". 276 $this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ". 277 $this->sqlite->quote_string($endTs->getTimestamp()).")"; 278 $res = $this->sqlite->query($query); 279 $arr = $this->sqlite->res2arr($res); 280 foreach($arr as $row) 281 { 282 if(isset($row['calendardata'])) 283 { 284 $entry = array(); 285 $vcal = \Sabre\VObject\Reader::read($row['calendardata']); 286 $start = $vcal->VEVENT->DTSTART; 287 if($start !== null) 288 { 289 $dtStart = $start->getDateTime(); 290 $dtStart->setTimezone($timezone); 291 $entry['start'] = $dtStart->format(\DateTime::ATOM); 292 if($start['VALUE'] == 'DATE') 293 $entry['allDay'] = true; 294 else 295 $entry['allDay'] = false; 296 } 297 $end = $vcal->VEVENT->DTEND; 298 if($end !== null) 299 { 300 $dtEnd = $end->getDateTime(); 301 $dtEnd->setTimezone($timezone); 302 $entry['end'] = $dtEnd->format(\DateTime::ATOM); 303 } 304 $description = $vcal->VEVENT->DESCRIPTION; 305 if($description !== null) 306 $entry['description'] = (string)$description; 307 else 308 $entry['description'] = ''; 309 $entry['title'] = (string)$vcal->VEVENT->summary; 310 $entry['id'] = $row['uid']; 311 $data[] = $entry; 312 } 313 } 314 return $data; 315 } 316 317 public function getEventWithUid($uid) 318 { 319 $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=". 320 $this->sqlite->quote_string($uid); 321 $res = $this->sqlite->query($query); 322 $row = $this->sqlite->res2row($res); 323 return $row; 324 } 325 326 public function editCalendarEntryForPage($id, $user, $params) 327 { 328 $settings = $this->getPersonalSettings($user); 329 if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') 330 $timezone = new \DateTimeZone($settings['timezone']); 331 else 332 $timezone = new \DateTimeZone('UTC'); 333 $startDate = explode('-', $params['eventfrom']); 334 $startTime = explode(':', $params['eventfromtime']); 335 $endDate = explode('-', $params['eventto']); 336 $endTime = explode(':', $params['eventtotime']); 337 $uid = $params['uid']; 338 $event = $this->getEventWithUid($uid); 339 require_once('vendor/autoload.php'); 340 if(!isset($event['calendardata'])) 341 return false; 342 $uri = $event['uri']; 343 $calid = $event['calendarid']; 344 $vcal = \Sabre\VObject\Reader::read($event['calendardata']); 345 $vevent = $vcal->VEVENT; 346 $vevent->summary = $params['eventname']; 347 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 348 $description = $params['eventdescription']; 349 $vevent->remove('DESCRIPTION'); 350 $vevent->remove('DTSTAMP'); 351 $vevent->remove('LAST-MODIFIED'); 352 $vevent->add('DTSTAMP', $dtStamp); 353 $vevent->add('LAST-MODIFIED', $dtStamp); 354 if($description !== '') 355 $vevent->add('DESCRIPTION', $description); 356 $dtStart = new \DateTime(); 357 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 358 if($params['allday'] != '1') 359 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 360 $dtStart->setTimezone($timezone); 361 $dtEnd = new \DateTime(); 362 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 363 if($params['allday'] != '1') 364 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 365 $dtEnd->setTimezone($timezone); 366 // According to the VCal spec, we need to add a whole day here 367 if($params['allday'] == '1') 368 $dtEnd->add(new \DateInterval('P1D')); 369 $vevent->remove('DTSTART'); 370 $vevent->remove('DTEND'); 371 $dtStartEv = $vevent->add('DTSTART', $dtStart); 372 $dtEndEv = $vevent->add('DTEND', $dtEnd); 373 if($params['allday'] == '1') 374 { 375 $dtStartEv['VALUE'] = 'DATE'; 376 $dtEndEv['VALUE'] = 'DATE'; 377 } 378 $now = new DateTime(); 379 $eventStr = $vcal->serialize(); 380 381 $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr). 382 ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()). 383 ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()). 384 ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()). 385 ", size=".strlen($eventStr). 386 ", etag=".$this->sqlite->quote_string(md5($eventStr)). 387 " WHERE uid=".$this->sqlite->quote_string($uid); 388 $res = $this->sqlite->query($query); 389 if($res !== false) 390 { 391 $this->updateSyncTokenLog($calid, $uri, 'modified'); 392 return true; 393 } 394 return false; 395 } 396 397 public function deleteCalendarEntryForPage($id, $params) 398 { 399 $uid = $params['uid']; 400 $event = $this->getEventWithUid($uid); 401 $calid = $event['calendarid']; 402 $uri = $event['uri']; 403 $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid); 404 $res = $this->sqlite->query($query); 405 if($res !== false) 406 { 407 $this->updateSyncTokenLog($calid, $uri, 'deleted'); 408 } 409 return true; 410 } 411 412 public function getSyncTokenForCalendar($calid) 413 { 414 $row = $this->getCalendarSettings($calid); 415 if(isset($row['synctoken'])) 416 return $row['synctoken']; 417 return false; 418 } 419 420 public function operationNameToOperation($operationName) 421 { 422 switch($operationName) 423 { 424 case 'added': 425 return 1; 426 break; 427 case 'modified': 428 return 2; 429 break; 430 case 'deleted': 431 return 3; 432 break; 433 } 434 return false; 435 } 436 437 private function updateSyncTokenLog($calid, $uri, $operation) 438 { 439 $currentToken = $this->getSyncTokenForCalendar($calid); 440 $operationCode = $this->operationNameToOperation($operation); 441 if(($operationCode === false) || ($currentToken === false)) 442 return false; 443 $values = array($uri, 444 $currentToken, 445 $calid, 446 $operationCode 447 ); 448 $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(". 449 $this->sqlite->quote_and_join($values, ',').")"; 450 $res = $this->sqlite->query($query); 451 if($res === false) 452 return false; 453 $currentToken++; 454 $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=". 455 $this->sqlite->quote_string($calid); 456 $res = $this->sqlite->query($query); 457 return ($res !== false); 458 } 459 460 public function getSyncUrlForPage($id, $user = null) 461 { 462 if(is_null($user)) 463 $user = $_SERVER['REMOTE_USER']; 464 465 $calid = $this->getCalendarIdForPage($id); 466 if($calid === false) 467 return false; 468 469 $calsettings = $this->getCalendarSettings($calid); 470 if(!isset($calsettings['uri'])) 471 return false; 472 473 $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; 474 return $syncurl; 475 } 476 477} 478