1<?php 2/** 3 * Helper Class for the DAVCal plugin 4 * This helper does the actual work. 5 * 6 */ 7 8// must be run within Dokuwiki 9if(!defined('DOKU_INC')) die(); 10 11class helper_plugin_davcal extends DokuWiki_Plugin { 12 13 protected $sqlite = null; 14 protected $cachedValues = array(); 15 16 /** 17 * Constructor to load the configuration and the SQLite plugin 18 */ 19 public function helper_plugin_davcal() { 20 $this->sqlite =& plugin_load('helper', 'sqlite'); 21 global $conf; 22 if($conf['allowdebug']) 23 dbglog('---- DAVCAL helper.php init'); 24 if(!$this->sqlite) 25 { 26 if($conf['allowdebug']) 27 dbglog('This plugin requires the sqlite plugin. Please install it.'); 28 msg('This plugin requires the sqlite plugin. Please install it.'); 29 return; 30 } 31 32 if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/')) 33 { 34 if($conf['allowdebug']) 35 dbglog('Error initialising the SQLite DB for DAVCal'); 36 return; 37 } 38 } 39 40 /** 41 * Retrieve meta data for a given page 42 * 43 * @param string $id optional The page ID 44 * @return array The metadata 45 */ 46 private function getMeta($id = null) { 47 global $ID; 48 global $INFO; 49 50 if ($id === null) $id = $ID; 51 52 if($ID === $id && $INFO['meta']) { 53 $meta = $INFO['meta']; 54 } else { 55 $meta = p_get_metadata($id); 56 } 57 58 return $meta; 59 } 60 61 /** 62 * Retrieve the meta data for a given page 63 * 64 * @param string $id optional The page ID 65 * @return array with meta data 66 */ 67 public function getCalendarMetaForPage($id = null) 68 { 69 if(is_null($id)) 70 { 71 global $ID; 72 $id = $ID; 73 } 74 75 $meta = $this->getMeta($id); 76 if(isset($meta['plugin_davcal'])) 77 return $meta['plugin_davcal']; 78 else 79 return array(); 80 } 81 82 /** 83 * Get all calendar pages used by a given page 84 * based on the stored metadata 85 * 86 * @param string $id optional The page id 87 * @return mixed The pages as array or false 88 */ 89 public function getCalendarPagesByMeta($id = null) 90 { 91 if(is_null($id)) 92 { 93 global $ID; 94 $id = $ID; 95 } 96 97 $meta = $this->getCalendarMetaForPage($id); 98 if(isset($meta['id'])) 99 return array_keys($meta['id']); 100 return false; 101 } 102 103 /** 104 * Get a list of calendar names/pages/ids/colors 105 * for an array of page ids 106 * 107 * @param array $calendarPages The calendar pages to retrieve 108 * @return array The list 109 */ 110 public function getCalendarMapForIDs($calendarPages) 111 { 112 $data = array(); 113 foreach($calendarPages as $page) 114 { 115 $calid = $this->getCalendarIdForPage($page); 116 if($calid !== false) 117 { 118 $settings = $this->getCalendarSettings($calid); 119 $name = $settings['displayname']; 120 $color = $settings['calendarcolor']; 121 $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid, 122 'color' => $color); 123 } 124 } 125 return $data; 126 } 127 128 /** 129 * Get the saved calendar color for a given page. 130 * 131 * @param string $id optional The page ID 132 * @return mixed The color on success, otherwise false 133 */ 134 public function getCalendarColorForPage($id = null) 135 { 136 if(is_null($id)) 137 { 138 global $ID; 139 $id = $ID; 140 } 141 142 $calid = $this->getCalendarIdForPage($id); 143 if($calid === false) 144 return false; 145 146 return $this->getCalendarColorForCalendar($calid); 147 } 148 149 /** 150 * Get the saved calendar color for a given calendar ID. 151 * 152 * @param string $id optional The calendar ID 153 * @return mixed The color on success, otherwise false 154 */ 155 public function getCalendarColorForCalendar($calid) 156 { 157 if(isset($this->cachedValues['calendarcolor'][$calid])) 158 return $this->cachedValues['calendarcolor'][$calid]; 159 160 $row = $this->getCalendarSettings($calid); 161 162 if(!isset($row['calendarcolor'])) 163 return false; 164 165 $color = $row['calendarcolor']; 166 $this->cachedValues['calendarcolor'][$calid] = $color; 167 return $color; 168 } 169 170 /** 171 * Get the user's principal URL for iOS sync 172 * @param string $user the user name 173 * @return the URL to the principal sync 174 */ 175 public function getPrincipalUrlForUser($user) 176 { 177 if(is_null($user)) 178 return false; 179 $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user; 180 return $url; 181 } 182 183 /** 184 * Set the calendar color for a given page. 185 * 186 * @param string $color The color definition 187 * @param string $id optional The page ID 188 * @return boolean True on success, otherwise false 189 */ 190 public function setCalendarColorForPage($color, $id = null) 191 { 192 if(is_null($id)) 193 { 194 global $ID; 195 $id = $ID; 196 } 197 $calid = $this->getCalendarIdForPage($id); 198 if($calid === false) 199 return false; 200 201 $query = "UPDATE calendars SET calendarcolor=".$this->sqlite->quote_string($color). 202 " WHERE id=".$this->sqlite->quote_string($calid); 203 $res = $this->sqlite->query($query); 204 if($res !== false) 205 { 206 $this->cachedValues['calendarcolor'][$calid] = $color; 207 return true; 208 } 209 return false; 210 } 211 212 /** 213 * Set the calendar name and description for a given page with a given 214 * page id. 215 * If the calendar doesn't exist, the calendar is created! 216 * 217 * @param string $name The name of the new calendar 218 * @param string $description The description of the new calendar 219 * @param string $id (optional) The ID of the page 220 * @param string $userid The userid of the creating user 221 * 222 * @return boolean True on success, otherwise false. 223 */ 224 public function setCalendarNameForPage($name, $description, $id = null, $userid = null) 225 { 226 if(is_null($id)) 227 { 228 global $ID; 229 $id = $ID; 230 } 231 if(is_null($userid)) 232 { 233 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 234 { 235 $userid = $_SERVER['REMOTE_USER']; 236 } 237 else 238 { 239 $userid = uniqid('davcal-'); 240 } 241 } 242 $calid = $this->getCalendarIdForPage($id); 243 if($calid === false) 244 return $this->createCalendarForPage($name, $description, $id, $userid); 245 246 $query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ". 247 "description=".$this->sqlite->quote_string($description)." WHERE ". 248 "id=".$this->sqlite->quote_string($calid); 249 $res = $this->sqlite->query($query); 250 if($res !== false) 251 return true; 252 return false; 253 } 254 255 /** 256 * Save the personal settings to the SQLite database 'calendarsettings'. 257 * 258 * @param array $settings The settings array to store 259 * @param string $userid (optional) The userid to store 260 * 261 * @param boolean True on success, otherwise false 262 */ 263 public function savePersonalSettings($settings, $userid = null) 264 { 265 if(is_null($userid)) 266 { 267 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 268 { 269 $userid = $_SERVER['REMOTE_USER']; 270 } 271 else 272 { 273 return false; 274 } 275 } 276 $this->sqlite->query("BEGIN TRANSACTION"); 277 278 $query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); 279 $this->sqlite->query($query); 280 281 foreach($settings as $key => $value) 282 { 283 $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (". 284 $this->sqlite->quote_string($userid).", ". 285 $this->sqlite->quote_string($key).", ". 286 $this->sqlite->quote_string($value).")"; 287 $res = $this->sqlite->query($query); 288 if($res === false) 289 return false; 290 } 291 $this->sqlite->query("COMMIT TRANSACTION"); 292 $this->cachedValues['settings'][$userid] = $settings; 293 return true; 294 } 295 296 /** 297 * Retrieve the settings array for a given user id. 298 * Some sane defaults are returned, currently: 299 * 300 * timezone => local 301 * weeknumbers => 0 302 * workweek => 0 303 * 304 * @param string $userid (optional) The user id to retrieve 305 * 306 * @return array The settings array 307 */ 308 public function getPersonalSettings($userid = null) 309 { 310 // Some sane default settings 311 $settings = array( 312 'timezone' => $this->getConf('timezone'), 313 'weeknumbers' => $this->getConf('weeknumbers'), 314 'workweek' => $this->getConf('workweek'), 315 'monday' => $this->getConf('monday'), 316 'timeformat' => $this->getConf('timeformat') 317 ); 318 if(is_null($userid)) 319 { 320 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 321 { 322 $userid = $_SERVER['REMOTE_USER']; 323 } 324 else 325 { 326 return $settings; 327 } 328 } 329 330 if(isset($this->cachedValues['settings'][$userid])) 331 return $this->cachedValues['settings'][$userid]; 332 $query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid); 333 $res = $this->sqlite->query($query); 334 $arr = $this->sqlite->res2arr($res); 335 foreach($arr as $row) 336 { 337 $settings[$row['key']] = $row['value']; 338 } 339 $this->cachedValues['settings'][$userid] = $settings; 340 return $settings; 341 } 342 343 /** 344 * Retrieve the calendar ID based on a page ID from the SQLite table 345 * 'pagetocalendarmapping'. 346 * 347 * @param string $id (optional) The page ID to retrieve the corresponding calendar 348 * 349 * @return mixed the ID on success, otherwise false 350 */ 351 public function getCalendarIdForPage($id = null) 352 { 353 if(is_null($id)) 354 { 355 global $ID; 356 $id = $ID; 357 } 358 359 if(isset($this->cachedValues['calid'][$id])) 360 return $this->cachedValues['calid'][$id]; 361 362 $query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id); 363 $res = $this->sqlite->query($query); 364 $row = $this->sqlite->res2row($res); 365 if(isset($row['calid'])) 366 { 367 $calid = $row['calid']; 368 $this->cachedValues['calid'] = $calid; 369 return $calid; 370 } 371 return false; 372 } 373 374 /** 375 * Retrieve the complete calendar id to page mapping. 376 * This is necessary to be able to retrieve a list of 377 * calendars for a given user and check the access rights. 378 * 379 * @return array The mapping array 380 */ 381 public function getCalendarIdToPageMapping() 382 { 383 $query = "SELECT calid, page FROM pagetocalendarmapping"; 384 $res = $this->sqlite->query($query); 385 $arr = $this->sqlite->res2arr($res); 386 return $arr; 387 } 388 389 /** 390 * Retrieve all calendar IDs a given user has access to. 391 * The user is specified by the principalUri, so the 392 * user name is actually split from the URI component. 393 * 394 * Access rights are checked against DokuWiki's ACL 395 * and applied accordingly. 396 * 397 * @param string $principalUri The principal URI to work on 398 * 399 * @return array An associative array of calendar IDs 400 */ 401 public function getCalendarIdsForUser($principalUri) 402 { 403 global $auth; 404 $user = explode('/', $principalUri); 405 $user = end($user); 406 $mapping = $this->getCalendarIdToPageMapping(); 407 $calids = array(); 408 $ud = $auth->getUserData($user); 409 $groups = $ud['grps']; 410 foreach($mapping as $row) 411 { 412 $id = $row['calid']; 413 $page = $row['page']; 414 $acl = auth_aclcheck($page, $user, $groups); 415 if($acl >= AUTH_READ) 416 { 417 $write = $acl > AUTH_READ; 418 $calids[$id] = array('readonly' => !$write); 419 } 420 } 421 return $calids; 422 } 423 424 /** 425 * Create a new calendar for a given page ID and set name and description 426 * accordingly. Also update the pagetocalendarmapping table on success. 427 * 428 * @param string $name The calendar's name 429 * @param string $description The calendar's description 430 * @param string $id (optional) The page ID to work on 431 * @param string $userid (optional) The user ID that created the calendar 432 * 433 * @return boolean True on success, otherwise false 434 */ 435 public function createCalendarForPage($name, $description, $id = null, $userid = null) 436 { 437 if(is_null($id)) 438 { 439 global $ID; 440 $id = $ID; 441 } 442 if(is_null($userid)) 443 { 444 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 445 { 446 $userid = $_SERVER['REMOTE_USER']; 447 } 448 else 449 { 450 $userid = uniqid('davcal-'); 451 } 452 } 453 $values = array('principals/'.$userid, 454 $name, 455 str_replace(array('/', ' ', ':'), '_', $id), 456 $description, 457 'VEVENT,VTODO', 458 0, 459 1); 460 $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");"; 461 $res = $this->sqlite->query($query); 462 if($res === false) 463 return false; 464 465 // Get the new calendar ID 466 $query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ". 467 "displayname=".$this->sqlite->quote_string($values[1])." AND ". 468 "uri=".$this->sqlite->quote_string($values[2])." AND ". 469 "description=".$this->sqlite->quote_string($values[3]); 470 $res = $this->sqlite->query($query); 471 $row = $this->sqlite->res2row($res); 472 473 // Update the pagetocalendarmapping table with the new calendar ID 474 if(isset($row['id'])) 475 { 476 $values = array($id, $row['id']); 477 $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; 478 $res = $this->sqlite->query($query); 479 return ($res !== false); 480 } 481 482 return false; 483 } 484 485 /** 486 * Add a new iCal entry for a given page, i.e. a given calendar. 487 * 488 * The parameter array needs to contain 489 * detectedtz => The timezone as detected by the browser 490 * currenttz => The timezone in use by the calendar 491 * eventfrom => The event's start date 492 * eventfromtime => The event's start time 493 * eventto => The event's end date 494 * eventtotime => The event's end time 495 * eventname => The event's name 496 * eventdescription => The event's description 497 * 498 * @param string $id The page ID to work on 499 * @param string $user The user who created the calendar 500 * @param string $params A parameter array with values to create 501 * 502 * @return boolean True on success, otherwise false 503 */ 504 public function addCalendarEntryToCalendarForPage($id, $user, $params) 505 { 506 if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') 507 $timezone = new \DateTimeZone($params['currenttz']); 508 elseif($params['currenttz'] === 'local') 509 $timezone = new \DateTimeZone($params['detectedtz']); 510 else 511 $timezone = new \DateTimeZone('UTC'); 512 513 // Retrieve dates from settings 514 $startDate = explode('-', $params['eventfrom']); 515 $startTime = explode(':', $params['eventfromtime']); 516 $endDate = explode('-', $params['eventto']); 517 $endTime = explode(':', $params['eventtotime']); 518 519 // Load SabreDAV 520 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 521 $vcalendar = new \Sabre\VObject\Component\VCalendar(); 522 523 // Add VCalendar, UID and Event Name 524 $event = $vcalendar->add('VEVENT'); 525 $uuid = \Sabre\VObject\UUIDUtil::getUUID(); 526 $event->add('UID', $uuid); 527 $event->summary = $params['eventname']; 528 529 // Add a description if requested 530 $description = $params['eventdescription']; 531 if($description !== '') 532 $event->add('DESCRIPTION', $description); 533 534 // Add attachments 535 $attachments = $params['attachments']; 536 if(!is_null($attachments)) 537 foreach($attachments as $attachment) 538 $event->add('ATTACH', $attachment); 539 540 // Create a timestamp for last modified, created and dtstamp values in UTC 541 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 542 $event->add('DTSTAMP', $dtStamp); 543 $event->add('CREATED', $dtStamp); 544 $event->add('LAST-MODIFIED', $dtStamp); 545 546 // Adjust the start date, based on the given timezone information 547 $dtStart = new \DateTime(); 548 $dtStart->setTimezone($timezone); 549 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 550 551 // Only add the time values if it's not an allday event 552 if($params['allday'] != '1') 553 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 554 555 // Adjust the end date, based on the given timezone information 556 $dtEnd = new \DateTime(); 557 $dtEnd->setTimezone($timezone); 558 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 559 560 // Only add the time values if it's not an allday event 561 if($params['allday'] != '1') 562 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 563 564 // According to the VCal spec, we need to add a whole day here 565 if($params['allday'] == '1') 566 $dtEnd->add(new \DateInterval('P1D')); 567 568 // Really add Start and End events 569 $dtStartEv = $event->add('DTSTART', $dtStart); 570 $dtEndEv = $event->add('DTEND', $dtEnd); 571 572 // Adjust the DATE format for allday events 573 if($params['allday'] == '1') 574 { 575 $dtStartEv['VALUE'] = 'DATE'; 576 $dtEndEv['VALUE'] = 'DATE'; 577 } 578 579 // Actually add the values to the database 580 $calid = $this->getCalendarIdForPage($id); 581 $uri = uniqid('dokuwiki-').'.ics'; 582 $now = new DateTime(); 583 $eventStr = $vcalendar->serialize(); 584 585 $values = array($calid, 586 $uri, 587 $eventStr, 588 $now->getTimestamp(), 589 'VEVENT', 590 $event->DTSTART->getDateTime()->getTimeStamp(), 591 $event->DTEND->getDateTime()->getTimeStamp(), 592 strlen($eventStr), 593 md5($eventStr), 594 $uuid 595 ); 596 597 $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")"; 598 $res = $this->sqlite->query($query); 599 600 // If successfully, update the sync token database 601 if($res !== false) 602 { 603 $this->updateSyncTokenLog($calid, $uri, 'added'); 604 return true; 605 } 606 return false; 607 } 608 609 /** 610 * Retrieve the calendar settings of a given calendar id 611 * 612 * @param string $calid The calendar ID 613 * 614 * @return array The calendar settings array 615 */ 616 public function getCalendarSettings($calid) 617 { 618 $query = "SELECT principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid); 619 $res = $this->sqlite->query($query); 620 $row = $this->sqlite->res2row($res); 621 return $row; 622 } 623 624 /** 625 * Retrieve all events that are within a given date range, 626 * based on the timezone setting. 627 * 628 * There is also support for retrieving recurring events, 629 * using Sabre's VObject Iterator. Recurring events are represented 630 * as individual calendar entries with the same UID. 631 * 632 * @param string $id The page ID to work with 633 * @param string $user The user ID to work with 634 * @param string $startDate The start date as a string 635 * @param string $endDate The end date as a string 636 * 637 * @return array An array containing the calendar entries. 638 */ 639 public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone) 640 { 641 if($timezone !== '' && $timezone !== 'local') 642 $timezone = new \DateTimeZone($timezone); 643 else 644 $timezone = new \DateTimeZone('UTC'); 645 $data = array(); 646 647 // Load SabreDAV 648 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 649 $calid = $this->getCalendarIdForPage($id); 650 $color = $this->getCalendarColorForCalendar($calid); 651 $startTs = new \DateTime($startDate); 652 $endTs = new \DateTime($endDate); 653 654 // Retrieve matching calendar objects 655 $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid=". 656 $this->sqlite->quote_string($calid)." AND firstoccurence < ". 657 $this->sqlite->quote_string($endTs->getTimestamp())." AND lastoccurence > ". 658 $this->sqlite->quote_string($startTs->getTimestamp()); 659 $res = $this->sqlite->query($query); 660 $arr = $this->sqlite->res2arr($res); 661 662 // Parse individual calendar entries 663 foreach($arr as $row) 664 { 665 if(isset($row['calendardata'])) 666 { 667 $entry = array(); 668 $vcal = \Sabre\VObject\Reader::read($row['calendardata']); 669 $recurrence = $vcal->VEVENT->RRULE; 670 // If it is a recurring event, pass it through Sabre's EventIterator 671 if($recurrence != null) 672 { 673 $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT)); 674 $rEvents->rewind(); 675 $done = false; 676 while($rEvents->valid() && !$done) 677 { 678 $event = $rEvents->getEventObject(); 679 // If we are after the given time range, exit 680 if(($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()) && 681 ($rEvents->getDtEnd()->getTimestamp() > $endTs->getTimestamp())) 682 $done = true; 683 684 // If we are before the given time range, continue 685 if($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()) 686 { 687 $rEvents->next(); 688 continue; 689 } 690 691 // If we are within the given time range, parse the event 692 $data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true); 693 $rEvents->next(); 694 } 695 } 696 else 697 $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color); 698 } 699 } 700 return $data; 701 } 702 703 /** 704 * Helper function that parses the iCal data of a VEVENT to a calendar entry. 705 * 706 * @param \Sabre\VObject\VEvent $event The event to parse 707 * @param \DateTimeZone $timezone The timezone object 708 * @param string $uid The entry's UID 709 * @param boolean $recurring (optional) Set to true to define a recurring event 710 * 711 * @return array The parse calendar entry 712 */ 713 private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false) 714 { 715 $entry = array(); 716 $start = $event->DTSTART; 717 // Parse only if the start date/time is present 718 if($start !== null) 719 { 720 $dtStart = $start->getDateTime(); 721 $dtStart->setTimezone($timezone); 722 $entry['start'] = $dtStart->format(\DateTime::ATOM); 723 if($start['VALUE'] == 'DATE') 724 $entry['allDay'] = true; 725 else 726 $entry['allDay'] = false; 727 } 728 $end = $event->DTEND; 729 // Parse onlyl if the end date/time is present 730 if($end !== null) 731 { 732 $dtEnd = $end->getDateTime(); 733 $dtEnd->setTimezone($timezone); 734 $entry['end'] = $dtEnd->format(\DateTime::ATOM); 735 } 736 $description = $event->DESCRIPTION; 737 if($description !== null) 738 $entry['description'] = (string)$description; 739 else 740 $entry['description'] = ''; 741 $attachments = $event->ATTACH; 742 if($attachments !== null) 743 { 744 $entry['attachments'] = array(); 745 foreach($attachments as $attachment) 746 $entry['attachments'][] = (string)$attachment; 747 } 748 $entry['title'] = (string)$event->summary; 749 $entry['id'] = $uid; 750 $entry['page'] = $page; 751 $entry['color'] = $color; 752 $entry['recurring'] = $recurring; 753 754 return $entry; 755 } 756 757 /** 758 * Retrieve an event by its UID 759 * 760 * @param string $uid The event's UID 761 * 762 * @return mixed The table row with the given event 763 */ 764 public function getEventWithUid($uid) 765 { 766 $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=". 767 $this->sqlite->quote_string($uid); 768 $res = $this->sqlite->query($query); 769 $row = $this->sqlite->res2row($res); 770 return $row; 771 } 772 773 /** 774 * Retrieve all calendar events for a given calendar ID 775 * 776 * @param string $calid The calendar's ID 777 * 778 * @return array An array containing all calendar data 779 */ 780 public function getAllCalendarEvents($calid) 781 { 782 $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=". 783 $this->sqlite->quote_string($calid); 784 $res = $this->sqlite->query($query); 785 $arr = $this->sqlite->res2arr($res); 786 return $arr; 787 } 788 789 /** 790 * Edit a calendar entry for a page, given by its parameters. 791 * The params array has the same format as @see addCalendarEntryForPage 792 * 793 * @param string $id The page's ID to work on 794 * @param string $user The user's ID to work on 795 * @param array $params The parameter array for the edited calendar event 796 * 797 * @return boolean True on success, otherwise false 798 */ 799 public function editCalendarEntryForPage($id, $user, $params) 800 { 801 if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') 802 $timezone = new \DateTimeZone($params['currenttz']); 803 elseif($params['currenttz'] === 'local') 804 $timezone = new \DateTimeZone($params['detectedtz']); 805 else 806 $timezone = new \DateTimeZone('UTC'); 807 808 // Parse dates 809 $startDate = explode('-', $params['eventfrom']); 810 $startTime = explode(':', $params['eventfromtime']); 811 $endDate = explode('-', $params['eventto']); 812 $endTime = explode(':', $params['eventtotime']); 813 814 // Retrieve the existing event based on the UID 815 $uid = $params['uid']; 816 $event = $this->getEventWithUid($uid); 817 818 // Load SabreDAV 819 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 820 if(!isset($event['calendardata'])) 821 return false; 822 $uri = $event['uri']; 823 $calid = $event['calendarid']; 824 825 // Parse the existing event 826 $vcal = \Sabre\VObject\Reader::read($event['calendardata']); 827 $vevent = $vcal->VEVENT; 828 829 // Set the new event values 830 $vevent->summary = $params['eventname']; 831 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 832 $description = $params['eventdescription']; 833 834 // Remove existing timestamps to overwrite them 835 $vevent->remove('DESCRIPTION'); 836 $vevent->remove('DTSTAMP'); 837 $vevent->remove('LAST-MODIFIED'); 838 $vevent->remove('ATTACH'); 839 840 // Add new time stamps and description 841 $vevent->add('DTSTAMP', $dtStamp); 842 $vevent->add('LAST-MODIFIED', $dtStamp); 843 if($description !== '') 844 $vevent->add('DESCRIPTION', $description); 845 846 // Add attachments 847 $attachments = $params['attachments']; 848 if(!is_null($attachments)) 849 foreach($attachments as $attachment) 850 $vevent->add('ATTACH', $attachment); 851 852 // Setup DTSTART 853 $dtStart = new \DateTime(); 854 $dtStart->setTimezone($timezone); 855 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 856 if($params['allday'] != '1') 857 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 858 859 // Setup DTEND 860 $dtEnd = new \DateTime(); 861 $dtEnd->setTimezone($timezone); 862 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 863 if($params['allday'] != '1') 864 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 865 866 // According to the VCal spec, we need to add a whole day here 867 if($params['allday'] == '1') 868 $dtEnd->add(new \DateInterval('P1D')); 869 $vevent->remove('DTSTART'); 870 $vevent->remove('DTEND'); 871 $dtStartEv = $vevent->add('DTSTART', $dtStart); 872 $dtEndEv = $vevent->add('DTEND', $dtEnd); 873 874 // Remove the time for allday events 875 if($params['allday'] == '1') 876 { 877 $dtStartEv['VALUE'] = 'DATE'; 878 $dtEndEv['VALUE'] = 'DATE'; 879 } 880 $now = new DateTime(); 881 $eventStr = $vcal->serialize(); 882 883 // Actually write to the database 884 $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr). 885 ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()). 886 ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()). 887 ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()). 888 ", size=".strlen($eventStr). 889 ", etag=".$this->sqlite->quote_string(md5($eventStr)). 890 " WHERE uid=".$this->sqlite->quote_string($uid); 891 $res = $this->sqlite->query($query); 892 if($res !== false) 893 { 894 $this->updateSyncTokenLog($calid, $uri, 'modified'); 895 return true; 896 } 897 return false; 898 } 899 900 /** 901 * Delete a calendar entry for a given page. Actually, the event is removed 902 * based on the entry's UID, so that page ID is no used. 903 * 904 * @param string $id The page's ID (unused) 905 * @param array $params The parameter array to work with 906 * 907 * @return boolean True 908 */ 909 public function deleteCalendarEntryForPage($id, $params) 910 { 911 $uid = $params['uid']; 912 $event = $this->getEventWithUid($uid); 913 $calid = $event['calendarid']; 914 $uri = $event['uri']; 915 $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid); 916 $res = $this->sqlite->query($query); 917 if($res !== false) 918 { 919 $this->updateSyncTokenLog($calid, $uri, 'deleted'); 920 } 921 return true; 922 } 923 924 /** 925 * Retrieve the current sync token for a calendar 926 * 927 * @param string $calid The calendar id 928 * 929 * @return mixed The synctoken or false 930 */ 931 public function getSyncTokenForCalendar($calid) 932 { 933 $row = $this->getCalendarSettings($calid); 934 if(isset($row['synctoken'])) 935 return $row['synctoken']; 936 return false; 937 } 938 939 /** 940 * Helper function to convert the operation name to 941 * an operation code as stored in the database 942 * 943 * @param string $operationName The operation name 944 * 945 * @return mixed The operation code or false 946 */ 947 public function operationNameToOperation($operationName) 948 { 949 switch($operationName) 950 { 951 case 'added': 952 return 1; 953 break; 954 case 'modified': 955 return 2; 956 break; 957 case 'deleted': 958 return 3; 959 break; 960 } 961 return false; 962 } 963 964 /** 965 * Update the sync token log based on the calendar id and the 966 * operation that was performed. 967 * 968 * @param string $calid The calendar ID that was modified 969 * @param string $uri The calendar URI that was modified 970 * @param string $operation The operation that was performed 971 * 972 * @return boolean True on success, otherwise false 973 */ 974 private function updateSyncTokenLog($calid, $uri, $operation) 975 { 976 $currentToken = $this->getSyncTokenForCalendar($calid); 977 $operationCode = $this->operationNameToOperation($operation); 978 if(($operationCode === false) || ($currentToken === false)) 979 return false; 980 $values = array($uri, 981 $currentToken, 982 $calid, 983 $operationCode 984 ); 985 $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(". 986 $this->sqlite->quote_and_join($values, ',').")"; 987 $res = $this->sqlite->query($query); 988 if($res === false) 989 return false; 990 $currentToken++; 991 $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=". 992 $this->sqlite->quote_string($calid); 993 $res = $this->sqlite->query($query); 994 return ($res !== false); 995 } 996 997 /** 998 * Return the sync URL for a given Page, i.e. a calendar 999 * 1000 * @param string $id The page's ID 1001 * @param string $user (optional) The user's ID 1002 * 1003 * @return mixed The sync url or false 1004 */ 1005 public function getSyncUrlForPage($id, $user = null) 1006 { 1007 if(is_null($userid)) 1008 { 1009 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 1010 { 1011 $userid = $_SERVER['REMOTE_USER']; 1012 } 1013 else 1014 { 1015 return false; 1016 } 1017 } 1018 1019 $calid = $this->getCalendarIdForPage($id); 1020 if($calid === false) 1021 return false; 1022 1023 $calsettings = $this->getCalendarSettings($calid); 1024 if(!isset($calsettings['uri'])) 1025 return false; 1026 1027 $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; 1028 return $syncurl; 1029 } 1030 1031 /** 1032 * Return the private calendar's URL for a given page 1033 * 1034 * @param string $id the page ID 1035 * 1036 * @return mixed The private URL or false 1037 */ 1038 public function getPrivateURLForPage($id) 1039 { 1040 $calid = $this->getCalendarIdForPage($id); 1041 if($calid === false) 1042 return false; 1043 1044 return $this->getPrivateURLForCalendar($calid); 1045 } 1046 1047 /** 1048 * Return the private calendar's URL for a given calendar ID 1049 * 1050 * @param string $calid The calendar's ID 1051 * 1052 * @return mixed The private URL or false 1053 */ 1054 public function getPrivateURLForCalendar($calid) 1055 { 1056 if(isset($this->cachedValues['privateurl'][$calid])) 1057 return $this->cachedValues['privateurl'][$calid]; 1058 $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid); 1059 $res = $this->sqlite->query($query); 1060 $row = $this->sqlite->res2row($res); 1061 if(!isset($row['url'])) 1062 { 1063 $url = uniqid("dokuwiki-").".ics"; 1064 $values = array( 1065 $url, 1066 $calid 1067 ); 1068 $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(". 1069 $this->sqlite->quote_and_join($values, ", ").")"; 1070 $res = $this->sqlite->query($query); 1071 if($res === false) 1072 return false; 1073 } 1074 else 1075 { 1076 $url = $row['url']; 1077 } 1078 1079 $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url; 1080 $this->cachedValues['privateurl'][$calid] = $url; 1081 return $url; 1082 } 1083 1084 /** 1085 * Retrieve the calendar ID for a given private calendar URL 1086 * 1087 * @param string $url The private URL 1088 * 1089 * @return mixed The calendar ID or false 1090 */ 1091 public function getCalendarForPrivateURL($url) 1092 { 1093 $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url); 1094 $res = $this->sqlite->query($query); 1095 $row = $this->sqlite->res2row($res); 1096 if(!isset($row['calid'])) 1097 return false; 1098 return $row['calid']; 1099 } 1100 1101 /** 1102 * Return a given calendar as ICS feed, i.e. all events in one ICS file. 1103 * 1104 * @param string $caldi The calendar ID to retrieve 1105 * 1106 * @return mixed The calendar events as string or false 1107 */ 1108 public function getCalendarAsICSFeed($calid) 1109 { 1110 $calSettings = $this->getCalendarSettings($calid); 1111 if($calSettings === false) 1112 return false; 1113 $events = $this->getAllCalendarEvents($calid); 1114 if($events === false) 1115 return false; 1116 1117 $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:"; 1118 $out .= $calSettings['displayname']."\n"; 1119 foreach($events as $event) 1120 { 1121 $out .= rtrim($event['calendardata']); 1122 $out .= "\n"; 1123 } 1124 $out .= "END:VCALENDAR\n"; 1125 return $out; 1126 } 1127 1128 /** 1129 * Retrieve a configuration option for the plugin 1130 * 1131 * @param string $key The key to query 1132 * @return mixed The option set, null if not found 1133 */ 1134 public function getConfig($key) 1135 { 1136 return $this->getConf($key); 1137 } 1138 1139} 1140