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