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