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 = ? ". 202 " WHERE id = ?"; 203 $res = $this->sqlite->query($query, $color, $calid); 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 = ?, description = ? WHERE id = ?"; 247 $res = $this->sqlite->query($query, $name, $description, $calid); 248 if($res !== false) 249 return true; 250 return false; 251 } 252 253 /** 254 * Save the personal settings to the SQLite database 'calendarsettings'. 255 * 256 * @param array $settings The settings array to store 257 * @param string $userid (optional) The userid to store 258 * 259 * @param boolean True on success, otherwise false 260 */ 261 public function savePersonalSettings($settings, $userid = null) 262 { 263 if(is_null($userid)) 264 { 265 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 266 { 267 $userid = $_SERVER['REMOTE_USER']; 268 } 269 else 270 { 271 return false; 272 } 273 } 274 $this->sqlite->query("BEGIN TRANSACTION"); 275 276 $query = "DELETE FROM calendarsettings WHERE userid = ?"; 277 $this->sqlite->query($query, $userid); 278 279 foreach($settings as $key => $value) 280 { 281 $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)"; 282 $res = $this->sqlite->query($query, $userid, $key, $value); 283 if($res === false) 284 return false; 285 } 286 $this->sqlite->query("COMMIT TRANSACTION"); 287 $this->cachedValues['settings'][$userid] = $settings; 288 return true; 289 } 290 291 /** 292 * Retrieve the settings array for a given user id. 293 * Some sane defaults are returned, currently: 294 * 295 * timezone => local 296 * weeknumbers => 0 297 * workweek => 0 298 * 299 * @param string $userid (optional) The user id to retrieve 300 * 301 * @return array The settings array 302 */ 303 public function getPersonalSettings($userid = null) 304 { 305 // Some sane default settings 306 $settings = array( 307 'timezone' => $this->getConf('timezone'), 308 'weeknumbers' => $this->getConf('weeknumbers'), 309 'workweek' => $this->getConf('workweek'), 310 'monday' => $this->getConf('monday'), 311 'timeformat' => $this->getConf('timeformat') 312 ); 313 if(is_null($userid)) 314 { 315 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 316 { 317 $userid = $_SERVER['REMOTE_USER']; 318 } 319 else 320 { 321 return $settings; 322 } 323 } 324 325 if(isset($this->cachedValues['settings'][$userid])) 326 return $this->cachedValues['settings'][$userid]; 327 $query = "SELECT key, value FROM calendarsettings WHERE userid = ?"; 328 $res = $this->sqlite->query($query, $userid); 329 $arr = $this->sqlite->res2arr($res); 330 foreach($arr as $row) 331 { 332 $settings[$row['key']] = $row['value']; 333 } 334 $this->cachedValues['settings'][$userid] = $settings; 335 return $settings; 336 } 337 338 /** 339 * Retrieve the calendar ID based on a page ID from the SQLite table 340 * 'pagetocalendarmapping'. 341 * 342 * @param string $id (optional) The page ID to retrieve the corresponding calendar 343 * 344 * @return mixed the ID on success, otherwise false 345 */ 346 public function getCalendarIdForPage($id = null) 347 { 348 if(is_null($id)) 349 { 350 global $ID; 351 $id = $ID; 352 } 353 354 if(isset($this->cachedValues['calid'][$id])) 355 return $this->cachedValues['calid'][$id]; 356 357 $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?"; 358 $res = $this->sqlite->query($query, $id); 359 $row = $this->sqlite->res2row($res); 360 if(isset($row['calid'])) 361 { 362 $calid = $row['calid']; 363 $this->cachedValues['calid'] = $calid; 364 return $calid; 365 } 366 return false; 367 } 368 369 /** 370 * Retrieve the complete calendar id to page mapping. 371 * This is necessary to be able to retrieve a list of 372 * calendars for a given user and check the access rights. 373 * 374 * @return array The mapping array 375 */ 376 public function getCalendarIdToPageMapping() 377 { 378 $query = "SELECT calid, page FROM pagetocalendarmapping"; 379 $res = $this->sqlite->query($query); 380 $arr = $this->sqlite->res2arr($res); 381 return $arr; 382 } 383 384 /** 385 * Retrieve all calendar IDs a given user has access to. 386 * The user is specified by the principalUri, so the 387 * user name is actually split from the URI component. 388 * 389 * Access rights are checked against DokuWiki's ACL 390 * and applied accordingly. 391 * 392 * @param string $principalUri The principal URI to work on 393 * 394 * @return array An associative array of calendar IDs 395 */ 396 public function getCalendarIdsForUser($principalUri) 397 { 398 global $auth; 399 $user = explode('/', $principalUri); 400 $user = end($user); 401 $mapping = $this->getCalendarIdToPageMapping(); 402 $calids = array(); 403 $ud = $auth->getUserData($user); 404 $groups = $ud['grps']; 405 foreach($mapping as $row) 406 { 407 $id = $row['calid']; 408 $page = $row['page']; 409 $acl = auth_aclcheck($page, $user, $groups); 410 if($acl >= AUTH_READ) 411 { 412 $write = $acl > AUTH_READ; 413 $calids[$id] = array('readonly' => !$write); 414 } 415 } 416 return $calids; 417 } 418 419 /** 420 * Create a new calendar for a given page ID and set name and description 421 * accordingly. Also update the pagetocalendarmapping table on success. 422 * 423 * @param string $name The calendar's name 424 * @param string $description The calendar's description 425 * @param string $id (optional) The page ID to work on 426 * @param string $userid (optional) The user ID that created the calendar 427 * 428 * @return boolean True on success, otherwise false 429 */ 430 public function createCalendarForPage($name, $description, $id = null, $userid = null) 431 { 432 if(is_null($id)) 433 { 434 global $ID; 435 $id = $ID; 436 } 437 if(is_null($userid)) 438 { 439 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 440 { 441 $userid = $_SERVER['REMOTE_USER']; 442 } 443 else 444 { 445 $userid = uniqid('davcal-'); 446 } 447 } 448 $values = array('principals/'.$userid, 449 $name, 450 str_replace(array('/', ' ', ':'), '_', $id), 451 $description, 452 'VEVENT,VTODO', 453 0, 454 1); 455 $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ". 456 "VALUES (?, ?, ?, ?, ?, ?, ?)"; 457 $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]); 458 if($res === false) 459 return false; 460 461 // Get the new calendar ID 462 $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ". 463 "uri = ? AND description = ?"; 464 $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3]); 465 $row = $this->sqlite->res2row($res); 466 467 // Update the pagetocalendarmapping table with the new calendar ID 468 if(isset($row['id'])) 469 { 470 $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)"; 471 $res = $this->sqlite->query($query, $id, $row['id']); 472 return ($res !== false); 473 } 474 475 return false; 476 } 477 478 /** 479 * Add a new iCal entry for a given page, i.e. a given calendar. 480 * 481 * The parameter array needs to contain 482 * detectedtz => The timezone as detected by the browser 483 * currenttz => The timezone in use by the calendar 484 * eventfrom => The event's start date 485 * eventfromtime => The event's start time 486 * eventto => The event's end date 487 * eventtotime => The event's end time 488 * eventname => The event's name 489 * eventdescription => The event's description 490 * 491 * @param string $id The page ID to work on 492 * @param string $user The user who created the calendar 493 * @param string $params A parameter array with values to create 494 * 495 * @return boolean True on success, otherwise false 496 */ 497 public function addCalendarEntryToCalendarForPage($id, $user, $params) 498 { 499 if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') 500 $timezone = new \DateTimeZone($params['currenttz']); 501 elseif($params['currenttz'] === 'local') 502 $timezone = new \DateTimeZone($params['detectedtz']); 503 else 504 $timezone = new \DateTimeZone('UTC'); 505 506 // Retrieve dates from settings 507 $startDate = explode('-', $params['eventfrom']); 508 $startTime = explode(':', $params['eventfromtime']); 509 $endDate = explode('-', $params['eventto']); 510 $endTime = explode(':', $params['eventtotime']); 511 512 // Load SabreDAV 513 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 514 $vcalendar = new \Sabre\VObject\Component\VCalendar(); 515 516 // Add VCalendar, UID and Event Name 517 $event = $vcalendar->add('VEVENT'); 518 $uuid = \Sabre\VObject\UUIDUtil::getUUID(); 519 $event->add('UID', $uuid); 520 $event->summary = $params['eventname']; 521 522 // Add a description if requested 523 $description = $params['eventdescription']; 524 if($description !== '') 525 $event->add('DESCRIPTION', $description); 526 527 // Add attachments 528 $attachments = $params['attachments']; 529 if(!is_null($attachments)) 530 foreach($attachments as $attachment) 531 $event->add('ATTACH', $attachment); 532 533 // Create a timestamp for last modified, created and dtstamp values in UTC 534 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 535 $event->add('DTSTAMP', $dtStamp); 536 $event->add('CREATED', $dtStamp); 537 $event->add('LAST-MODIFIED', $dtStamp); 538 539 // Adjust the start date, based on the given timezone information 540 $dtStart = new \DateTime(); 541 $dtStart->setTimezone($timezone); 542 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 543 544 // Only add the time values if it's not an allday event 545 if($params['allday'] != '1') 546 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 547 548 // Adjust the end date, based on the given timezone information 549 $dtEnd = new \DateTime(); 550 $dtEnd->setTimezone($timezone); 551 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 552 553 // Only add the time values if it's not an allday event 554 if($params['allday'] != '1') 555 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 556 557 // According to the VCal spec, we need to add a whole day here 558 if($params['allday'] == '1') 559 $dtEnd->add(new \DateInterval('P1D')); 560 561 // Really add Start and End events 562 $dtStartEv = $event->add('DTSTART', $dtStart); 563 $dtEndEv = $event->add('DTEND', $dtEnd); 564 565 // Adjust the DATE format for allday events 566 if($params['allday'] == '1') 567 { 568 $dtStartEv['VALUE'] = 'DATE'; 569 $dtEndEv['VALUE'] = 'DATE'; 570 } 571 572 // Actually add the values to the database 573 $calid = $this->getCalendarIdForPage($id); 574 $uri = uniqid('dokuwiki-').'.ics'; 575 $now = new DateTime(); 576 $eventStr = $vcalendar->serialize(); 577 578 $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; 579 $res = $this->sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT', 580 $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(), 581 strlen($eventStr), md5($eventStr), $uuid); 582 583 // If successfully, update the sync token database 584 if($res !== false) 585 { 586 $this->updateSyncTokenLog($calid, $uri, 'added'); 587 return true; 588 } 589 return false; 590 } 591 592 /** 593 * Retrieve the calendar settings of a given calendar id 594 * 595 * @param string $calid The calendar ID 596 * 597 * @return array The calendar settings array 598 */ 599 public function getCalendarSettings($calid) 600 { 601 $query = "SELECT principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id= ? "; 602 $res = $this->sqlite->query($query, $calid); 603 $row = $this->sqlite->res2row($res); 604 return $row; 605 } 606 607 /** 608 * Retrieve all events that are within a given date range, 609 * based on the timezone setting. 610 * 611 * There is also support for retrieving recurring events, 612 * using Sabre's VObject Iterator. Recurring events are represented 613 * as individual calendar entries with the same UID. 614 * 615 * @param string $id The page ID to work with 616 * @param string $user The user ID to work with 617 * @param string $startDate The start date as a string 618 * @param string $endDate The end date as a string 619 * 620 * @return array An array containing the calendar entries. 621 */ 622 public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone) 623 { 624 if($timezone !== '' && $timezone !== 'local') 625 $timezone = new \DateTimeZone($timezone); 626 else 627 $timezone = new \DateTimeZone('UTC'); 628 $data = array(); 629 630 // Load SabreDAV 631 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 632 $calid = $this->getCalendarIdForPage($id); 633 $color = $this->getCalendarColorForCalendar($calid); 634 $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?"; 635 $startTs = null; 636 $endTs = null; 637 if($startDate !== null) 638 { 639 $startTs = new \DateTime($startDate); 640 $query .= " AND lastoccurence > ".$this->sqlite->quote_string($startTs->getTimestamp()); 641 } 642 if($endDate !== null) 643 { 644 $endTs = new \DateTime($endDate); 645 $query .= " AND firstoccurence < ".$this->sqlite->quote_string($endTs->getTimestamp()); 646 } 647 648 // Retrieve matching calendar objects 649 $res = $this->sqlite->query($query, $calid); 650 $arr = $this->sqlite->res2arr($res); 651 652 // Parse individual calendar entries 653 foreach($arr as $row) 654 { 655 if(isset($row['calendardata'])) 656 { 657 $entry = array(); 658 $vcal = \Sabre\VObject\Reader::read($row['calendardata']); 659 $recurrence = $vcal->VEVENT->RRULE; 660 // If it is a recurring event, pass it through Sabre's EventIterator 661 if($recurrence != null) 662 { 663 $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT)); 664 $rEvents->rewind(); 665 while($rEvents->valid()) 666 { 667 $event = $rEvents->getEventObject(); 668 // If we are after the given time range, exit 669 if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp())) 670 break; 671 672 // If we are before the given time range, continue 673 if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp())) 674 { 675 $rEvents->next(); 676 continue; 677 } 678 679 // If we are within the given time range, parse the event 680 $data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true); 681 $rEvents->next(); 682 } 683 } 684 else 685 $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color); 686 } 687 } 688 return $data; 689 } 690 691 /** 692 * Helper function that parses the iCal data of a VEVENT to a calendar entry. 693 * 694 * @param \Sabre\VObject\VEvent $event The event to parse 695 * @param \DateTimeZone $timezone The timezone object 696 * @param string $uid The entry's UID 697 * @param boolean $recurring (optional) Set to true to define a recurring event 698 * 699 * @return array The parse calendar entry 700 */ 701 private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false) 702 { 703 $entry = array(); 704 $start = $event->DTSTART; 705 // Parse only if the start date/time is present 706 if($start !== null) 707 { 708 $dtStart = $start->getDateTime(); 709 $dtStart->setTimezone($timezone); 710 711 // moment.js doesn't like times be given even if 712 // allDay is set to true 713 // This should fix T23 714 if($start['VALUE'] == 'DATE') 715 { 716 $entry['allDay'] = true; 717 $entry['start'] = $dtStart->format("Y-m-d"); 718 } 719 else 720 { 721 $entry['allDay'] = false; 722 $entry['start'] = $dtStart->format(\DateTime::ATOM); 723 } 724 } 725 $end = $event->DTEND; 726 // Parse only if the end date/time is present 727 if($end !== null) 728 { 729 $dtEnd = $end->getDateTime(); 730 $dtEnd->setTimezone($timezone); 731 if($end['VALUE'] == 'DATE') 732 $entry['end'] = $dtEnd->format("Y-m-d"); 733 else 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 $res = $this->sqlite->query($query, $uid); 768 $row = $this->sqlite->res2row($res); 769 return $row; 770 } 771 772 /** 773 * Retrieve all calendar events for a given calendar ID 774 * 775 * @param string $calid The calendar's ID 776 * 777 * @return array An array containing all calendar data 778 */ 779 public function getAllCalendarEvents($calid) 780 { 781 $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?"; 782 $res = $this->sqlite->query($query, $calid); 783 $arr = $this->sqlite->res2arr($res); 784 return $arr; 785 } 786 787 /** 788 * Edit a calendar entry for a page, given by its parameters. 789 * The params array has the same format as @see addCalendarEntryForPage 790 * 791 * @param string $id The page's ID to work on 792 * @param string $user The user's ID to work on 793 * @param array $params The parameter array for the edited calendar event 794 * 795 * @return boolean True on success, otherwise false 796 */ 797 public function editCalendarEntryForPage($id, $user, $params) 798 { 799 if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') 800 $timezone = new \DateTimeZone($params['currenttz']); 801 elseif($params['currenttz'] === 'local') 802 $timezone = new \DateTimeZone($params['detectedtz']); 803 else 804 $timezone = new \DateTimeZone('UTC'); 805 806 // Parse dates 807 $startDate = explode('-', $params['eventfrom']); 808 $startTime = explode(':', $params['eventfromtime']); 809 $endDate = explode('-', $params['eventto']); 810 $endTime = explode(':', $params['eventtotime']); 811 812 // Retrieve the existing event based on the UID 813 $uid = $params['uid']; 814 $event = $this->getEventWithUid($uid); 815 816 // Load SabreDAV 817 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 818 if(!isset($event['calendardata'])) 819 return false; 820 $uri = $event['uri']; 821 $calid = $event['calendarid']; 822 823 // Parse the existing event 824 $vcal = \Sabre\VObject\Reader::read($event['calendardata']); 825 $vevent = $vcal->VEVENT; 826 827 // Set the new event values 828 $vevent->summary = $params['eventname']; 829 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 830 $description = $params['eventdescription']; 831 832 // Remove existing timestamps to overwrite them 833 $vevent->remove('DESCRIPTION'); 834 $vevent->remove('DTSTAMP'); 835 $vevent->remove('LAST-MODIFIED'); 836 $vevent->remove('ATTACH'); 837 838 // Add new time stamps and description 839 $vevent->add('DTSTAMP', $dtStamp); 840 $vevent->add('LAST-MODIFIED', $dtStamp); 841 if($description !== '') 842 $vevent->add('DESCRIPTION', $description); 843 844 // Add attachments 845 $attachments = $params['attachments']; 846 if(!is_null($attachments)) 847 foreach($attachments as $attachment) 848 $vevent->add('ATTACH', $attachment); 849 850 // Setup DTSTART 851 $dtStart = new \DateTime(); 852 $dtStart->setTimezone($timezone); 853 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 854 if($params['allday'] != '1') 855 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 856 857 // Setup DTEND 858 $dtEnd = new \DateTime(); 859 $dtEnd->setTimezone($timezone); 860 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 861 if($params['allday'] != '1') 862 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 863 864 // According to the VCal spec, we need to add a whole day here 865 if($params['allday'] == '1') 866 $dtEnd->add(new \DateInterval('P1D')); 867 $vevent->remove('DTSTART'); 868 $vevent->remove('DTEND'); 869 $dtStartEv = $vevent->add('DTSTART', $dtStart); 870 $dtEndEv = $vevent->add('DTEND', $dtEnd); 871 872 // Remove the time for allday events 873 if($params['allday'] == '1') 874 { 875 $dtStartEv['VALUE'] = 'DATE'; 876 $dtEndEv['VALUE'] = 'DATE'; 877 } 878 $now = new DateTime(); 879 $eventStr = $vcal->serialize(); 880 // Actually write to the database 881 $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ". 882 "firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?"; 883 $res = $this->sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(), 884 $dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid); 885 if($res !== false) 886 { 887 $this->updateSyncTokenLog($calid, $uri, 'modified'); 888 return true; 889 } 890 return false; 891 } 892 893 /** 894 * Delete a calendar entry for a given page. Actually, the event is removed 895 * based on the entry's UID, so that page ID is no used. 896 * 897 * @param string $id The page's ID (unused) 898 * @param array $params The parameter array to work with 899 * 900 * @return boolean True 901 */ 902 public function deleteCalendarEntryForPage($id, $params) 903 { 904 $uid = $params['uid']; 905 $event = $this->getEventWithUid($uid); 906 $calid = $event['calendarid']; 907 $uri = $event['uri']; 908 $query = "DELETE FROM calendarobjects WHERE uid = ?"; 909 $res = $this->sqlite->query($query, $uid); 910 if($res !== false) 911 { 912 $this->updateSyncTokenLog($calid, $uri, 'deleted'); 913 } 914 return true; 915 } 916 917 /** 918 * Retrieve the current sync token for a calendar 919 * 920 * @param string $calid The calendar id 921 * 922 * @return mixed The synctoken or false 923 */ 924 public function getSyncTokenForCalendar($calid) 925 { 926 $row = $this->getCalendarSettings($calid); 927 if(isset($row['synctoken'])) 928 return $row['synctoken']; 929 return false; 930 } 931 932 /** 933 * Helper function to convert the operation name to 934 * an operation code as stored in the database 935 * 936 * @param string $operationName The operation name 937 * 938 * @return mixed The operation code or false 939 */ 940 public function operationNameToOperation($operationName) 941 { 942 switch($operationName) 943 { 944 case 'added': 945 return 1; 946 break; 947 case 'modified': 948 return 2; 949 break; 950 case 'deleted': 951 return 3; 952 break; 953 } 954 return false; 955 } 956 957 /** 958 * Update the sync token log based on the calendar id and the 959 * operation that was performed. 960 * 961 * @param string $calid The calendar ID that was modified 962 * @param string $uri The calendar URI that was modified 963 * @param string $operation The operation that was performed 964 * 965 * @return boolean True on success, otherwise false 966 */ 967 private function updateSyncTokenLog($calid, $uri, $operation) 968 { 969 $currentToken = $this->getSyncTokenForCalendar($calid); 970 $operationCode = $this->operationNameToOperation($operation); 971 if(($operationCode === false) || ($currentToken === false)) 972 return false; 973 $values = array($uri, 974 $currentToken, 975 $calid, 976 $operationCode 977 ); 978 $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)"; 979 $res = $this->sqlite->query($query, $uri, $currentToken, $calid, $operationCode); 980 if($res === false) 981 return false; 982 $currentToken++; 983 $query = "UPDATE calendars SET synctoken = ? WHERE id = ?"; 984 $res = $this->sqlite->query($query, $currentToken, $calid); 985 return ($res !== false); 986 } 987 988 /** 989 * Return the sync URL for a given Page, i.e. a calendar 990 * 991 * @param string $id The page's ID 992 * @param string $user (optional) The user's ID 993 * 994 * @return mixed The sync url or false 995 */ 996 public function getSyncUrlForPage($id, $user = null) 997 { 998 if(is_null($userid)) 999 { 1000 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 1001 { 1002 $userid = $_SERVER['REMOTE_USER']; 1003 } 1004 else 1005 { 1006 return false; 1007 } 1008 } 1009 1010 $calid = $this->getCalendarIdForPage($id); 1011 if($calid === false) 1012 return false; 1013 1014 $calsettings = $this->getCalendarSettings($calid); 1015 if(!isset($calsettings['uri'])) 1016 return false; 1017 1018 $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri']; 1019 return $syncurl; 1020 } 1021 1022 /** 1023 * Return the private calendar's URL for a given page 1024 * 1025 * @param string $id the page ID 1026 * 1027 * @return mixed The private URL or false 1028 */ 1029 public function getPrivateURLForPage($id) 1030 { 1031 $calid = $this->getCalendarIdForPage($id); 1032 if($calid === false) 1033 return false; 1034 1035 return $this->getPrivateURLForCalendar($calid); 1036 } 1037 1038 /** 1039 * Return the private calendar's URL for a given calendar ID 1040 * 1041 * @param string $calid The calendar's ID 1042 * 1043 * @return mixed The private URL or false 1044 */ 1045 public function getPrivateURLForCalendar($calid) 1046 { 1047 if(isset($this->cachedValues['privateurl'][$calid])) 1048 return $this->cachedValues['privateurl'][$calid]; 1049 $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?"; 1050 $res = $this->sqlite->query($query, $calid); 1051 $row = $this->sqlite->res2row($res); 1052 if(!isset($row['url'])) 1053 { 1054 $url = uniqid("dokuwiki-").".ics"; 1055 $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)"; 1056 $res = $this->sqlite->query($query, $url, $calid); 1057 if($res === false) 1058 return false; 1059 } 1060 else 1061 { 1062 $url = $row['url']; 1063 } 1064 1065 $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url; 1066 $this->cachedValues['privateurl'][$calid] = $url; 1067 return $url; 1068 } 1069 1070 /** 1071 * Retrieve the calendar ID for a given private calendar URL 1072 * 1073 * @param string $url The private URL 1074 * 1075 * @return mixed The calendar ID or false 1076 */ 1077 public function getCalendarForPrivateURL($url) 1078 { 1079 $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?"; 1080 $res = $this->sqlite->query($query, $url); 1081 $row = $this->sqlite->res2row($res); 1082 if(!isset($row['calid'])) 1083 return false; 1084 return $row['calid']; 1085 } 1086 1087 /** 1088 * Return a given calendar as ICS feed, i.e. all events in one ICS file. 1089 * 1090 * @param string $calid The calendar ID to retrieve 1091 * 1092 * @return mixed The calendar events as string or false 1093 */ 1094 public function getCalendarAsICSFeed($calid) 1095 { 1096 $calSettings = $this->getCalendarSettings($calid); 1097 if($calSettings === false) 1098 return false; 1099 $events = $this->getAllCalendarEvents($calid); 1100 if($events === false) 1101 return false; 1102 1103 // Load SabreDAV 1104 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 1105 $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:"; 1106 $out .= $calSettings['displayname']."\r\n"; 1107 foreach($events as $event) 1108 { 1109 $vcal = \Sabre\VObject\Reader::read($event['calendardata']); 1110 $evt = $vcal->VEVENT; 1111 $out .= $evt->serialize(); 1112 } 1113 $out .= "END:VCALENDAR\r\n"; 1114 return $out; 1115 } 1116 1117 /** 1118 * Retrieve a configuration option for the plugin 1119 * 1120 * @param string $key The key to query 1121 * @return mixed The option set, null if not found 1122 */ 1123 public function getConfig($key) 1124 { 1125 return $this->getConf($key); 1126 } 1127 1128} 1129