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 elseif($settings['timezone'] === 'local') 186 $timezone = new \DateTimeZone($params['detectedtz']); 187 else 188 $timezone = new \DateTimeZone('UTC'); 189 $startDate = explode('-', $params['eventfrom']); 190 $startTime = explode(':', $params['eventfromtime']); 191 $endDate = explode('-', $params['eventto']); 192 $endTime = explode(':', $params['eventtotime']); 193 require_once('vendor/autoload.php'); 194 $vcalendar = new \Sabre\VObject\Component\VCalendar(); 195 $event = $vcalendar->add('VEVENT'); 196 $uuid = \Sabre\VObject\UUIDUtil::getUUID(); 197 $event->add('UID', $uuid); 198 $event->summary = $params['eventname']; 199 $description = $params['eventdescription']; 200 if($description !== '') 201 $event->add('DESCRIPTION', $description); 202 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 203 $event->add('DTSTAMP', $dtStamp); 204 $event->add('CREATED', $dtStamp); 205 $event->add('LAST-MODIFIED', $dtStamp); 206 $dtStart = new \DateTime(); 207 $dtStart->setTimezone($timezone); 208 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 209 if($params['allday'] != '1') 210 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 211 $dtEnd = new \DateTime(); 212 $dtEnd->setTimezone($timezone); 213 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 214 if($params['allday'] != '1') 215 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 216 // According to the VCal spec, we need to add a whole day here 217 if($params['allday'] == '1') 218 $dtEnd->add(new \DateInterval('P1D')); 219 $dtStartEv = $event->add('DTSTART', $dtStart); 220 $dtEndEv = $event->add('DTEND', $dtEnd); 221 if($params['allday'] == '1') 222 { 223 $dtStartEv['VALUE'] = 'DATE'; 224 $dtEndEv['VALUE'] = 'DATE'; 225 } 226 $calid = $this->getCalendarIdForPage($id); 227 $uri = uniqid('dokuwiki-').'.ics'; 228 $now = new DateTime(); 229 $eventStr = $vcalendar->serialize(); 230 231 $values = array($calid, 232 $uri, 233 $eventStr, 234 $now->getTimestamp(), 235 'VEVENT', 236 $event->DTSTART->getDateTime()->getTimeStamp(), 237 $event->DTEND->getDateTime()->getTimeStamp(), 238 strlen($eventStr), 239 md5($eventStr), 240 uniqid() 241 ); 242 243 $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; 244 $res = $this->sqlite->query($query); 245 if($res !== false) 246 { 247 $this->updateSyncTokenLog($calid, $uri, 'added'); 248 return true; 249 } 250 return false; 251 } 252 253 public function getCalendarSettings($calid) 254 { 255 $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); 256 $res = $this->sqlite->query($query); 257 $row = $this->sqlite->res2row($res); 258 return $row; 259 } 260 261 public function getEventsWithinDateRange($id, $user, $startDate, $endDate) 262 { 263 $settings = $this->getPersonalSettings($user); 264 if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') 265 $timezone = new \DateTimeZone($settings['timezone']); 266 else 267 $timezone = new \DateTimeZone('UTC'); 268 $data = array(); 269 require_once('vendor/autoload.php'); 270 $calid = $this->getCalendarIdForPage($id); 271 $startTs = new \DateTime($startDate); 272 $endTs = new \DateTime($endDate); 273 $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid=". 274 $this->sqlite->quote_string($calid)." AND firstoccurence < ". 275 $this->sqlite->quote_string($endTs->getTimestamp())." AND lastoccurence > ". 276 $this->sqlite->quote_string($startTs->getTimestamp()); 277 $res = $this->sqlite->query($query); 278 $arr = $this->sqlite->res2arr($res); 279 foreach($arr as $row) 280 { 281 if(isset($row['calendardata'])) 282 { 283 $entry = array(); 284 $vcal = \Sabre\VObject\Reader::read($row['calendardata']); 285 $recurrence = $vcal->VEVENT->RRULE; 286 if($recurrence != null) 287 { 288 $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT)); 289 $rEvents->rewind(); 290 $done = false; 291 while($rEvents->valid() && !$done) 292 { 293 $event = $rEvents->getEventObject(); 294 if(($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()) && 295 ($rEvents->getDtEnd()->getTimestamp() > $endTs->getTimestamp())) 296 $done = true; 297 if($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()) 298 { 299 $rEvents->next(); 300 continue; 301 } 302 $data[] = $this->convertIcalDataToEntry($event, $timezone, $row['uid']); 303 $rEvents->next(); 304 } 305 } 306 else 307 $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $timezone, $row['uid']); 308 } 309 } 310 return $data; 311 } 312 313 private function convertIcalDataToEntry($event, $timezone, $uid) 314 { 315 $entry = array(); 316 $start = $event->DTSTART; 317 if($start !== null) 318 { 319 $dtStart = $start->getDateTime(); 320 $dtStart->setTimezone($timezone); 321 $entry['start'] = $dtStart->format(\DateTime::ATOM); 322 if($start['VALUE'] == 'DATE') 323 $entry['allDay'] = true; 324 else 325 $entry['allDay'] = false; 326 } 327 $end = $event->DTEND; 328 if($end !== null) 329 { 330 $dtEnd = $end->getDateTime(); 331 $dtEnd->setTimezone($timezone); 332 $entry['end'] = $dtEnd->format(\DateTime::ATOM); 333 } 334 $description = $event->DESCRIPTION; 335 if($description !== null) 336 $entry['description'] = (string)$description; 337 else 338 $entry['description'] = ''; 339 $entry['title'] = (string)$event->summary; 340 $entry['id'] = $uid; 341 return $entry; 342 } 343 344 public function getEventWithUid($uid) 345 { 346 $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=". 347 $this->sqlite->quote_string($uid); 348 $res = $this->sqlite->query($query); 349 $row = $this->sqlite->res2row($res); 350 return $row; 351 } 352 353 public function getAllCalendarEvents($calid) 354 { 355 $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=". 356 $this->sqlite->quote_string($calid); 357 $res = $this->sqlite->query($query); 358 $arr = $this->sqlite->res2arr($res); 359 return $arr; 360 } 361 362 public function editCalendarEntryForPage($id, $user, $params) 363 { 364 $settings = $this->getPersonalSettings($user); 365 if($settings['timezone'] !== '' && $settings['timezone'] !== 'local') 366 $timezone = new \DateTimeZone($settings['timezone']); 367 elseif($settings['timezone'] === 'local') 368 $timezone = new \DateTimeZone($params['detectedtz']); 369 else 370 $timezone = new \DateTimeZone('UTC'); 371 $startDate = explode('-', $params['eventfrom']); 372 $startTime = explode(':', $params['eventfromtime']); 373 $endDate = explode('-', $params['eventto']); 374 $endTime = explode(':', $params['eventtotime']); 375 $uid = $params['uid']; 376 $event = $this->getEventWithUid($uid); 377 require_once('vendor/autoload.php'); 378 if(!isset($event['calendardata'])) 379 return false; 380 $uri = $event['uri']; 381 $calid = $event['calendarid']; 382 $vcal = \Sabre\VObject\Reader::read($event['calendardata']); 383 $vevent = $vcal->VEVENT; 384 $vevent->summary = $params['eventname']; 385 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 386 $description = $params['eventdescription']; 387 $vevent->remove('DESCRIPTION'); 388 $vevent->remove('DTSTAMP'); 389 $vevent->remove('LAST-MODIFIED'); 390 $vevent->add('DTSTAMP', $dtStamp); 391 $vevent->add('LAST-MODIFIED', $dtStamp); 392 if($description !== '') 393 $vevent->add('DESCRIPTION', $description); 394 $dtStart = new \DateTime(); 395 $dtStart->setTimezone($timezone); 396 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 397 if($params['allday'] != '1') 398 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 399 $dtEnd = new \DateTime(); 400 $dtEnd->setTimezone($timezone); 401 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 402 if($params['allday'] != '1') 403 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 404 // According to the VCal spec, we need to add a whole day here 405 if($params['allday'] == '1') 406 $dtEnd->add(new \DateInterval('P1D')); 407 $vevent->remove('DTSTART'); 408 $vevent->remove('DTEND'); 409 $dtStartEv = $vevent->add('DTSTART', $dtStart); 410 $dtEndEv = $vevent->add('DTEND', $dtEnd); 411 if($params['allday'] == '1') 412 { 413 $dtStartEv['VALUE'] = 'DATE'; 414 $dtEndEv['VALUE'] = 'DATE'; 415 } 416 $now = new DateTime(); 417 $eventStr = $vcal->serialize(); 418 419 $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr). 420 ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()). 421 ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()). 422 ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()). 423 ", size=".strlen($eventStr). 424 ", etag=".$this->sqlite->quote_string(md5($eventStr)). 425 " WHERE uid=".$this->sqlite->quote_string($uid); 426 $res = $this->sqlite->query($query); 427 if($res !== false) 428 { 429 $this->updateSyncTokenLog($calid, $uri, 'modified'); 430 return true; 431 } 432 return false; 433 } 434 435 public function deleteCalendarEntryForPage($id, $params) 436 { 437 $uid = $params['uid']; 438 $event = $this->getEventWithUid($uid); 439 $calid = $event['calendarid']; 440 $uri = $event['uri']; 441 $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid); 442 $res = $this->sqlite->query($query); 443 if($res !== false) 444 { 445 $this->updateSyncTokenLog($calid, $uri, 'deleted'); 446 } 447 return true; 448 } 449 450 public function getSyncTokenForCalendar($calid) 451 { 452 $row = $this->getCalendarSettings($calid); 453 if(isset($row['synctoken'])) 454 return $row['synctoken']; 455 return false; 456 } 457 458 public function operationNameToOperation($operationName) 459 { 460 switch($operationName) 461 { 462 case 'added': 463 return 1; 464 break; 465 case 'modified': 466 return 2; 467 break; 468 case 'deleted': 469 return 3; 470 break; 471 } 472 return false; 473 } 474 475 private function updateSyncTokenLog($calid, $uri, $operation) 476 { 477 $currentToken = $this->getSyncTokenForCalendar($calid); 478 $operationCode = $this->operationNameToOperation($operation); 479 if(($operationCode === false) || ($currentToken === false)) 480 return false; 481 $values = array($uri, 482 $currentToken, 483 $calid, 484 $operationCode 485 ); 486 $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(". 487 $this->sqlite->quote_and_join($values, ',').")"; 488 $res = $this->sqlite->query($query); 489 if($res === false) 490 return false; 491 $currentToken++; 492 $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=". 493 $this->sqlite->quote_string($calid); 494 $res = $this->sqlite->query($query); 495 return ($res !== false); 496 } 497 498 public function getSyncUrlForPage($id, $user = null) 499 { 500 if(is_null($user)) 501 $user = $_SERVER['REMOTE_USER']; 502 503 $calid = $this->getCalendarIdForPage($id); 504 if($calid === false) 505 return false; 506 507 $calsettings = $this->getCalendarSettings($calid); 508 if(!isset($calsettings['uri'])) 509 return false; 510 511 $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; 512 return $syncurl; 513 } 514 515 public function getPrivateURLForPage($id) 516 { 517 $calid = $this->getCalendarIdForPage($id); 518 if($calid === false) 519 return false; 520 521 return $this->getPrivateURLForCalendar($calid); 522 } 523 524 public function getPrivateURLForCalendar($calid) 525 { 526 $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid); 527 $res = $this->sqlite->query($query); 528 $row = $this->sqlite->res2row($res); 529 if(!isset($row['url'])) 530 { 531 $url = uniqid("dokuwiki-").".ics"; 532 $values = array( 533 $url, 534 $calid 535 ); 536 $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(". 537 $this->sqlite->quote_and_join($values, ", ").")"; 538 $res = $this->sqlite->query($query); 539 if($res === false) 540 return false; 541 } 542 else 543 { 544 $url = $row['url']; 545 } 546 return DOKU_URL.'lib/plugins/davcal/ics.php/'.$url; 547 } 548 549 public function getCalendarForPrivateURL($url) 550 { 551 $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url); 552 $res = $this->sqlite->query($query); 553 $row = $this->sqlite->res2row($res); 554 if(!isset($row['calid'])) 555 return false; 556 return $row['calid']; 557 } 558 559 public function getCalendarAsICSFeed($calid) 560 { 561 $calSettings = $this->getCalendarSettings($calid); 562 if($calSettings === false) 563 return false; 564 $events = $this->getAllCalendarEvents($calid); 565 if($events === false) 566 return false; 567 568 $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:"; 569 $out .= $calSettings['displayname']."\n"; 570 foreach($events as $event) 571 { 572 $out .= rtrim($event['calendardata']); 573 $out .= "\n"; 574 } 575 $out .= "END:VCALENDAR\n"; 576 return $out; 577 } 578 579} 580