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