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