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