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