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