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