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 if(empty($pages)) 123 return false; 124 return $pages; 125 } 126 return false; 127 } 128 129 /** 130 * Get a list of calendar names/pages/ids/colors 131 * for an array of page ids 132 * 133 * @param array $calendarPages The calendar pages to retrieve 134 * @return array The list 135 */ 136 public function getCalendarMapForIDs($calendarPages) 137 { 138 $data = array(); 139 foreach($calendarPages as $page => $color) 140 { 141 $calid = $this->getCalendarIdForPage($page); 142 if($calid !== false) 143 { 144 $settings = $this->getCalendarSettings($calid); 145 $name = $settings['displayname']; 146 $write = (auth_quickaclcheck($page) > AUTH_READ); 147 $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid, 148 'color' => $color, 'write' => $write); 149 } 150 } 151 return $data; 152 } 153 154 /** 155 * Get the saved calendar color for a given page. 156 * 157 * @param string $id optional The page ID 158 * @return mixed The color on success, otherwise false 159 */ 160 public function getCalendarColorForPage($id = null) 161 { 162 if(is_null($id)) 163 { 164 global $ID; 165 $id = $ID; 166 } 167 168 $calid = $this->getCalendarIdForPage($id); 169 if($calid === false) 170 return false; 171 172 return $this->getCalendarColorForCalendar($calid); 173 } 174 175 /** 176 * Get the saved calendar color for a given calendar ID. 177 * 178 * @param string $id optional The calendar ID 179 * @return mixed The color on success, otherwise false 180 */ 181 public function getCalendarColorForCalendar($calid) 182 { 183 if(isset($this->cachedValues['calendarcolor'][$calid])) 184 return $this->cachedValues['calendarcolor'][$calid]; 185 186 $row = $this->getCalendarSettings($calid); 187 188 if(!isset($row['calendarcolor'])) 189 return false; 190 191 $color = $row['calendarcolor']; 192 $this->cachedValues['calendarcolor'][$calid] = $color; 193 return $color; 194 } 195 196 /** 197 * Get the user's principal URL for iOS sync 198 * @param string $user the user name 199 * @return the URL to the principal sync 200 */ 201 public function getPrincipalUrlForUser($user) 202 { 203 if(is_null($user)) 204 return false; 205 $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user; 206 return $url; 207 } 208 209 /** 210 * Set the calendar color for a given page. 211 * 212 * @param string $color The color definition 213 * @param string $id optional The page ID 214 * @return boolean True on success, otherwise false 215 */ 216 public function setCalendarColorForPage($color, $id = null) 217 { 218 if(is_null($id)) 219 { 220 global $ID; 221 $id = $ID; 222 } 223 $calid = $this->getCalendarIdForPage($id); 224 if($calid === false) 225 return false; 226 227 $query = "UPDATE calendars SET calendarcolor = ? ". 228 " WHERE id = ?"; 229 $res = $this->sqlite->query($query, $color, $calid); 230 if($res !== false) 231 { 232 $this->cachedValues['calendarcolor'][$calid] = $color; 233 return true; 234 } 235 return false; 236 } 237 238 /** 239 * Set the calendar name and description for a given page with a given 240 * page id. 241 * If the calendar doesn't exist, the calendar is created! 242 * 243 * @param string $name The name of the new calendar 244 * @param string $description The description of the new calendar 245 * @param string $id (optional) The ID of the page 246 * @param string $userid The userid of the creating user 247 * 248 * @return boolean True on success, otherwise false. 249 */ 250 public function setCalendarNameForPage($name, $description, $id = null, $userid = null) 251 { 252 if(is_null($id)) 253 { 254 global $ID; 255 $id = $ID; 256 } 257 if(is_null($userid)) 258 { 259 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 260 { 261 $userid = $_SERVER['REMOTE_USER']; 262 } 263 else 264 { 265 $userid = uniqid('davcal-'); 266 } 267 } 268 $calid = $this->getCalendarIdForPage($id); 269 if($calid === false) 270 return $this->createCalendarForPage($name, $description, $id, $userid); 271 272 $query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?"; 273 $res = $this->sqlite->query($query, $name, $description, $calid); 274 if($res !== false) 275 return true; 276 return false; 277 } 278 279 /** 280 * Save the personal settings to the SQLite database 'calendarsettings'. 281 * 282 * @param array $settings The settings array to store 283 * @param string $userid (optional) The userid to store 284 * 285 * @param boolean True on success, otherwise false 286 */ 287 public function savePersonalSettings($settings, $userid = null) 288 { 289 if(is_null($userid)) 290 { 291 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 292 { 293 $userid = $_SERVER['REMOTE_USER']; 294 } 295 else 296 { 297 return false; 298 } 299 } 300 $this->sqlite->query("BEGIN TRANSACTION"); 301 302 $query = "DELETE FROM calendarsettings WHERE userid = ?"; 303 $this->sqlite->query($query, $userid); 304 305 foreach($settings as $key => $value) 306 { 307 $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)"; 308 $res = $this->sqlite->query($query, $userid, $key, $value); 309 if($res === false) 310 return false; 311 } 312 $this->sqlite->query("COMMIT TRANSACTION"); 313 $this->cachedValues['settings'][$userid] = $settings; 314 return true; 315 } 316 317 /** 318 * Retrieve the settings array for a given user id. 319 * Some sane defaults are returned, currently: 320 * 321 * timezone => local 322 * weeknumbers => 0 323 * workweek => 0 324 * 325 * @param string $userid (optional) The user id to retrieve 326 * 327 * @return array The settings array 328 */ 329 public function getPersonalSettings($userid = null) 330 { 331 // Some sane default settings 332 $settings = array( 333 'timezone' => $this->getConf('timezone'), 334 'weeknumbers' => $this->getConf('weeknumbers'), 335 'workweek' => $this->getConf('workweek'), 336 'monday' => $this->getConf('monday'), 337 'timeformat' => $this->getConf('timeformat') 338 ); 339 if(is_null($userid)) 340 { 341 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 342 { 343 $userid = $_SERVER['REMOTE_USER']; 344 } 345 else 346 { 347 return $settings; 348 } 349 } 350 351 if(isset($this->cachedValues['settings'][$userid])) 352 return $this->cachedValues['settings'][$userid]; 353 $query = "SELECT key, value FROM calendarsettings WHERE userid = ?"; 354 $res = $this->sqlite->query($query, $userid); 355 $arr = $this->sqlite->res2arr($res); 356 foreach($arr as $row) 357 { 358 $settings[$row['key']] = $row['value']; 359 } 360 $this->cachedValues['settings'][$userid] = $settings; 361 return $settings; 362 } 363 364 /** 365 * Retrieve the calendar ID based on a page ID from the SQLite table 366 * 'pagetocalendarmapping'. 367 * 368 * @param string $id (optional) The page ID to retrieve the corresponding calendar 369 * 370 * @return mixed the ID on success, otherwise false 371 */ 372 public function getCalendarIdForPage($id = null) 373 { 374 if(is_null($id)) 375 { 376 global $ID; 377 $id = $ID; 378 } 379 380 if(isset($this->cachedValues['calid'][$id])) 381 return $this->cachedValues['calid'][$id]; 382 383 $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?"; 384 $res = $this->sqlite->query($query, $id); 385 $row = $this->sqlite->res2row($res); 386 if(isset($row['calid'])) 387 { 388 $calid = $row['calid']; 389 $this->cachedValues['calid'] = $calid; 390 return $calid; 391 } 392 return false; 393 } 394 395 /** 396 * Retrieve the complete calendar id to page mapping. 397 * This is necessary to be able to retrieve a list of 398 * calendars for a given user and check the access rights. 399 * 400 * @return array The mapping array 401 */ 402 public function getCalendarIdToPageMapping() 403 { 404 $query = "SELECT calid, page FROM pagetocalendarmapping"; 405 $res = $this->sqlite->query($query); 406 $arr = $this->sqlite->res2arr($res); 407 return $arr; 408 } 409 410 /** 411 * Retrieve all calendar IDs a given user has access to. 412 * The user is specified by the principalUri, so the 413 * user name is actually split from the URI component. 414 * 415 * Access rights are checked against DokuWiki's ACL 416 * and applied accordingly. 417 * 418 * @param string $principalUri The principal URI to work on 419 * 420 * @return array An associative array of calendar IDs 421 */ 422 public function getCalendarIdsForUser($principalUri) 423 { 424 global $auth; 425 $user = explode('/', $principalUri); 426 $user = end($user); 427 $mapping = $this->getCalendarIdToPageMapping(); 428 $calids = array(); 429 $ud = $auth->getUserData($user); 430 $groups = $ud['grps']; 431 foreach($mapping as $row) 432 { 433 $id = $row['calid']; 434 $page = $row['page']; 435 $acl = auth_aclcheck($page, $user, $groups); 436 if($acl >= AUTH_READ) 437 { 438 $write = $acl > AUTH_READ; 439 $calids[$id] = array('readonly' => !$write); 440 } 441 } 442 return $calids; 443 } 444 445 /** 446 * Create a new calendar for a given page ID and set name and description 447 * accordingly. Also update the pagetocalendarmapping table on success. 448 * 449 * @param string $name The calendar's name 450 * @param string $description The calendar's description 451 * @param string $id (optional) The page ID to work on 452 * @param string $userid (optional) The user ID that created the calendar 453 * 454 * @return boolean True on success, otherwise false 455 */ 456 public function createCalendarForPage($name, $description, $id = null, $userid = null) 457 { 458 if(is_null($id)) 459 { 460 global $ID; 461 $id = $ID; 462 } 463 if(is_null($userid)) 464 { 465 if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER'])) 466 { 467 $userid = $_SERVER['REMOTE_USER']; 468 } 469 else 470 { 471 $userid = uniqid('davcal-'); 472 } 473 } 474 $values = array('principals/'.$userid, 475 $name, 476 str_replace(array('/', ' ', ':'), '_', $id), 477 $description, 478 'VEVENT,VTODO', 479 0, 480 1); 481 $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ". 482 "VALUES (?, ?, ?, ?, ?, ?, ?)"; 483 $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]); 484 if($res === false) 485 return false; 486 487 // Get the new calendar ID 488 $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ". 489 "uri = ? AND description = ?"; 490 $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3]); 491 $row = $this->sqlite->res2row($res); 492 493 // Update the pagetocalendarmapping table with the new calendar ID 494 if(isset($row['id'])) 495 { 496 $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)"; 497 $res = $this->sqlite->query($query, $id, $row['id']); 498 return ($res !== false); 499 } 500 501 return false; 502 } 503 504 /** 505 * Add a new iCal entry for a given page, i.e. a given calendar. 506 * 507 * The parameter array needs to contain 508 * detectedtz => The timezone as detected by the browser 509 * currenttz => The timezone in use by the calendar 510 * eventfrom => The event's start date 511 * eventfromtime => The event's start time 512 * eventto => The event's end date 513 * eventtotime => The event's end time 514 * eventname => The event's name 515 * eventdescription => The event's description 516 * 517 * @param string $id The page ID to work on 518 * @param string $user The user who created the calendar 519 * @param string $params A parameter array with values to create 520 * 521 * @return boolean True on success, otherwise false 522 */ 523 public function addCalendarEntryToCalendarForPage($id, $user, $params) 524 { 525 if($params['currenttz'] !== '' && $params['currenttz'] !== 'local') 526 $timezone = new \DateTimeZone($params['currenttz']); 527 elseif($params['currenttz'] === 'local') 528 $timezone = new \DateTimeZone($params['detectedtz']); 529 else 530 $timezone = new \DateTimeZone('UTC'); 531 532 // Retrieve dates from settings 533 $startDate = explode('-', $params['eventfrom']); 534 $startTime = explode(':', $params['eventfromtime']); 535 $endDate = explode('-', $params['eventto']); 536 $endTime = explode(':', $params['eventtotime']); 537 538 // Load SabreDAV 539 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 540 $vcalendar = new \Sabre\VObject\Component\VCalendar(); 541 542 // Add VCalendar, UID and Event Name 543 $event = $vcalendar->add('VEVENT'); 544 $uuid = \Sabre\VObject\UUIDUtil::getUUID(); 545 $event->add('UID', $uuid); 546 $event->summary = $params['eventname']; 547 548 // Add a description if requested 549 $description = $params['eventdescription']; 550 if($description !== '') 551 $event->add('DESCRIPTION', $description); 552 553 // Add attachments 554 $attachments = $params['attachments']; 555 if(!is_null($attachments)) 556 foreach($attachments as $attachment) 557 $event->add('ATTACH', $attachment); 558 559 // Create a timestamp for last modified, created and dtstamp values in UTC 560 $dtStamp = new \DateTime(null, new \DateTimeZone('UTC')); 561 $event->add('DTSTAMP', $dtStamp); 562 $event->add('CREATED', $dtStamp); 563 $event->add('LAST-MODIFIED', $dtStamp); 564 565 // Adjust the start date, based on the given timezone information 566 $dtStart = new \DateTime(); 567 $dtStart->setTimezone($timezone); 568 $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2])); 569 570 // Only add the time values if it's not an allday event 571 if($params['allday'] != '1') 572 $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0); 573 574 // Adjust the end date, based on the given timezone information 575 $dtEnd = new \DateTime(); 576 $dtEnd->setTimezone($timezone); 577 $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2])); 578 579 // Only add the time values if it's not an allday event 580 if($params['allday'] != '1') 581 $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0); 582 583 // According to the VCal spec, we need to add a whole day here 584 if($params['allday'] == '1') 585 $dtEnd->add(new \DateInterval('P1D')); 586 587 // Really add Start and End events 588 $dtStartEv = $event->add('DTSTART', $dtStart); 589 $dtEndEv = $event->add('DTEND', $dtEnd); 590 591 // Adjust the DATE format for allday events 592 if($params['allday'] == '1') 593 { 594 $dtStartEv['VALUE'] = 'DATE'; 595 $dtEndEv['VALUE'] = 'DATE'; 596 } 597 598 // Actually add the values to the database 599 $calid = $this->getCalendarIdForPage($id); 600 $uri = uniqid('dokuwiki-').'.ics'; 601 $now = new DateTime(); 602 $eventStr = $vcalendar->serialize(); 603 604 $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; 605 $res = $this->sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT', 606 $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(), 607 strlen($eventStr), md5($eventStr), $uuid); 608 609 // If successfully, update the sync token database 610 if($res !== false) 611 { 612 $this->updateSyncTokenLog($calid, $uri, 'added'); 613 return true; 614 } 615 return false; 616 } 617 618 /** 619 * Retrieve the calendar settings of a given calendar id 620 * 621 * @param string $calid The calendar ID 622 * 623 * @return array The calendar settings array 624 */ 625 public function getCalendarSettings($calid) 626 { 627 $query = "SELECT principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id= ? "; 628 $res = $this->sqlite->query($query, $calid); 629 $row = $this->sqlite->res2row($res); 630 return $row; 631 } 632 633 /** 634 * Retrieve all events that are within a given date range, 635 * based on the timezone setting. 636 * 637 * There is also support for retrieving recurring events, 638 * using Sabre's VObject Iterator. Recurring events are represented 639 * as individual calendar entries with the same UID. 640 * 641 * @param string $id The page ID to work with 642 * @param string $user The user ID to work with 643 * @param string $startDate The start date as a string 644 * @param string $endDate The end date as a string 645 * @param string $color (optional) The calendar's color 646 * 647 * @return array An array containing the calendar entries. 648 */ 649 public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null) 650 { 651 if($timezone !== '' && $timezone !== 'local') 652 $timezone = new \DateTimeZone($timezone); 653 else 654 $timezone = new \DateTimeZone('UTC'); 655 $data = array(); 656 657 // Load SabreDAV 658 require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php'); 659 $calid = $this->getCalendarIdForPage($id); 660 if(is_null($color)) 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