xref: /plugin/davcal/helper.php (revision 82a48dfbf5495f3f2fd216265cb05b6e2f79ee7c)
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