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