xref: /plugin/davcal/helper.php (revision ebc4eb577f1c03164ce429a1a34a53fcac305745)
1<?php
2/**
3  * Helper Class for the tagrevisions plugin
4  * This helper does the actual work.
5  *
6  * Configurable in DokuWiki's configuration
7  */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12class helper_plugin_davcal extends DokuWiki_Plugin {
13
14  protected $sqlite = null;
15
16  /**
17    * Constructor to load the configuration
18    */
19  public function helper_plugin_davcal() {
20    $this->sqlite =& plugin_load('helper', 'sqlite');
21    if(!$this->sqlite)
22    {
23        msg('This plugin requires the sqlite plugin. Please install it.');
24        return;
25    }
26
27    if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/'))
28    {
29        return;
30    }
31  }
32
33  public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
34  {
35      if(is_null($id))
36      {
37          global $ID;
38          $id = $ID;
39      }
40      if(is_null($userid))
41        $userid = $_SERVER['REMOTE_USER'];
42      $calid = $this->getCalendarIdForPage($id);
43      if($calid === false)
44        return $this->createCalendarForPage($name, $description, $id, $userid);
45
46      $query = "UPDATE calendars SET displayname=".$this->sqlite->quote_string($name).", ".
47               "description=".$this->sqlite->quote_string($description)." WHERE ".
48               "id=".$this->sqlite->quote_string($calid);
49      $res = $this->sqlite->query($query);
50      if($res !== false)
51        return true;
52      return false;
53  }
54
55  public function savePersonalSettings($settings, $userid = null)
56  {
57      if(is_null($userid))
58          $userid = $_SERVER['REMOTE_USER'];
59      $this->sqlite->query("BEGIN TRANSACTION");
60
61      $query = "DELETE FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid);
62      $this->sqlite->query($query);
63
64      foreach($settings as $key => $value)
65      {
66          $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (".
67                   $this->sqlite->quote_string($userid).", ".
68                   $this->sqlite->quote_string($key).", ".
69                   $this->sqlite->quote_string($value).")";
70          $res = $this->sqlite->query($query);
71          if($res === false)
72              return false;
73      }
74      $this->sqlite->query("COMMIT TRANSACTION");
75      return true;
76  }
77
78  public function getPersonalSettings($userid = null)
79  {
80      if(is_null($userid))
81        $userid = $_SERVER['REMOTE_USER'];
82      // Some sane default settings
83      $settings = array(
84        'timezone' => 'local',
85        'weeknumbers' => '0',
86        'workweek' => '0'
87      );
88      $query = "SELECT key, value FROM calendarsettings WHERE userid=".$this->sqlite->quote_string($userid);
89      $res = $this->sqlite->query($query);
90      $arr = $this->sqlite->res2arr($res);
91      foreach($arr as $row)
92      {
93          $settings[$row['key']] = $row['value'];
94      }
95      return $settings;
96  }
97
98  public function getCalendarIdForPage($id = null)
99  {
100      if(is_null($id))
101      {
102          global $ID;
103          $id = $ID;
104      }
105
106      $query = "SELECT calid FROM pagetocalendarmapping WHERE page=".$this->sqlite->quote_string($id);
107      $res = $this->sqlite->query($query);
108      $row = $this->sqlite->res2row($res);
109      if(isset($row['calid']))
110        return $row['calid'];
111      else
112        return false;
113  }
114
115  public function getCalendarIdToPageMapping()
116  {
117      $query = "SELECT calid, page FROM pagetocalendarmapping";
118      $res = $this->sqlite->query($query);
119      $arr = $this->sqlite->res2arr($res);
120      return $arr;
121  }
122
123  public function getCalendarIdsForUser($principalUri)
124  {
125      $user = explode('/', $principalUri);
126      $user = end($user);
127      $mapping = $this->getCalendarIdToPageMapping();
128      $calids = array();
129      foreach($mapping as $row)
130      {
131          $id = $row['calid'];
132          $page = $row['page'];
133          $acl = auth_quickaclcheck($page);
134          if($acl >= AUTH_READ)
135          {
136              $write = $acl > AUTH_READ;
137              $calids[$id] = array('readonly' => !$write);
138          }
139      }
140      return $calids;
141  }
142
143  public function createCalendarForPage($name, $description, $id = null, $userid = null)
144  {
145      if(is_null($id))
146      {
147          global $ID;
148          $id = $ID;
149      }
150      if(is_null($userid))
151          $userid = $_SERVER['REMOTE_USER'];
152      $values = array('principals/'.$userid,
153                      $name,
154                      str_replace(array('/', ' ', ':'), '_', $id),
155                      $description,
156                      'VEVENT,VTODO',
157                      0,
158                      1);
159      $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) VALUES (".$this->sqlite->quote_and_join($values, ',').");";
160      $res = $this->sqlite->query($query);
161      if($res === false)
162        return false;
163      $query = "SELECT id FROM calendars WHERE principaluri=".$this->sqlite->quote_string($values[0])." AND ".
164               "displayname=".$this->sqlite->quote_string($values[1])." AND ".
165               "uri=".$this->sqlite->quote_string($values[2])." AND ".
166               "description=".$this->sqlite->quote_string($values[3]);
167      $res = $this->sqlite->query($query);
168      $row = $this->sqlite->res2row($res);
169      if(isset($row['id']))
170      {
171          $values = array($id, $row['id']);
172          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (".$this->sqlite->quote_and_join($values, ',').")";
173          $res = $this->sqlite->query($query);
174          return ($res !== false);
175      }
176
177      return false;
178  }
179
180  public function addCalendarEntryToCalendarForPage($id, $user, $params)
181  {
182      $settings = $this->getPersonalSettings($user);
183      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
184          $timezone = new \DateTimeZone($settings['timezone']);
185      elseif($settings['timezone'] === 'local')
186          $timezone = new \DateTimeZone($params['detectedtz']);
187      else
188          $timezone = new \DateTimeZone('UTC');
189      $startDate = explode('-', $params['eventfrom']);
190      $startTime = explode(':', $params['eventfromtime']);
191      $endDate = explode('-', $params['eventto']);
192      $endTime = explode(':', $params['eventtotime']);
193      require_once('vendor/autoload.php');
194      $vcalendar = new \Sabre\VObject\Component\VCalendar();
195      $event = $vcalendar->add('VEVENT');
196      $uuid = \Sabre\VObject\UUIDUtil::getUUID();
197      $event->add('UID', $uuid);
198      $event->summary = $params['eventname'];
199      $description = $params['eventdescription'];
200      if($description !== '')
201        $event->add('DESCRIPTION', $description);
202      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
203      $event->add('DTSTAMP', $dtStamp);
204      $event->add('CREATED', $dtStamp);
205      $event->add('LAST-MODIFIED', $dtStamp);
206      $dtStart = new \DateTime();
207      $dtStart->setTimezone($timezone);
208      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
209      if($params['allday'] != '1')
210        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
211      $dtEnd = new \DateTime();
212      $dtEnd->setTimezone($timezone);
213      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
214      if($params['allday'] != '1')
215        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
216      // According to the VCal spec, we need to add a whole day here
217      if($params['allday'] == '1')
218          $dtEnd->add(new \DateInterval('P1D'));
219      $dtStartEv = $event->add('DTSTART', $dtStart);
220      $dtEndEv = $event->add('DTEND', $dtEnd);
221      if($params['allday'] == '1')
222      {
223          $dtStartEv['VALUE'] = 'DATE';
224          $dtEndEv['VALUE'] = 'DATE';
225      }
226      $calid = $this->getCalendarIdForPage($id);
227      $uri = uniqid('dokuwiki-').'.ics';
228      $now = new DateTime();
229      $eventStr = $vcalendar->serialize();
230
231      $values = array($calid,
232                      $uri,
233                      $eventStr,
234                      $now->getTimestamp(),
235                      'VEVENT',
236                      $event->DTSTART->getDateTime()->getTimeStamp(),
237                      $event->DTEND->getDateTime()->getTimeStamp(),
238                      strlen($eventStr),
239                      md5($eventStr),
240                      uniqid()
241      );
242
243      $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")";
244      $res = $this->sqlite->query($query);
245      if($res !== false)
246      {
247          $this->updateSyncTokenLog($calid, $uri, 'added');
248          return true;
249      }
250      return false;
251  }
252
253  public function getCalendarSettings($calid)
254  {
255      $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid);
256      $res = $this->sqlite->query($query);
257      $row = $this->sqlite->res2row($res);
258      return $row;
259  }
260
261  public function getEventsWithinDateRange($id, $user, $startDate, $endDate)
262  {
263      $settings = $this->getPersonalSettings($user);
264      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
265          $timezone = new \DateTimeZone($settings['timezone']);
266      else
267          $timezone = new \DateTimeZone('UTC');
268      $data = array();
269      require_once('vendor/autoload.php');
270      $calid = $this->getCalendarIdForPage($id);
271      $startTs = new \DateTime($startDate);
272      $endTs = new \DateTime($endDate);
273      $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid=".
274                $this->sqlite->quote_string($calid)." AND firstoccurence < ".
275                $this->sqlite->quote_string($endTs->getTimestamp())." AND lastoccurence > ".
276                $this->sqlite->quote_string($startTs->getTimestamp());
277      $res = $this->sqlite->query($query);
278      $arr = $this->sqlite->res2arr($res);
279      foreach($arr as $row)
280      {
281          if(isset($row['calendardata']))
282          {
283              $entry = array();
284              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
285              $recurrence = $vcal->VEVENT->RRULE;
286              if($recurrence != null)
287              {
288                  $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT));
289                  $rEvents->rewind();
290                  $done = false;
291                  while($rEvents->valid() && !$done)
292                  {
293                      $event = $rEvents->getEventObject();
294                      if(($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()) &&
295                         ($rEvents->getDtEnd()->getTimestamp() > $endTs->getTimestamp()))
296                        $done = true;
297                      if($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp())
298                      {
299                          $rEvents->next();
300                          continue;
301                      }
302                      $data[] = $this->convertIcalDataToEntry($event, $timezone, $row['uid']);
303                      $rEvents->next();
304                  }
305              }
306              else
307                $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $timezone, $row['uid']);
308          }
309      }
310      return $data;
311  }
312
313  private function convertIcalDataToEntry($event, $timezone, $uid)
314  {
315      $entry = array();
316      $start = $event->DTSTART;
317      if($start !== null)
318      {
319        $dtStart = $start->getDateTime();
320        $dtStart->setTimezone($timezone);
321        $entry['start'] = $dtStart->format(\DateTime::ATOM);
322        if($start['VALUE'] == 'DATE')
323          $entry['allDay'] = true;
324        else
325          $entry['allDay'] = false;
326      }
327      $end = $event->DTEND;
328      if($end !== null)
329      {
330        $dtEnd = $end->getDateTime();
331        $dtEnd->setTimezone($timezone);
332        $entry['end'] = $dtEnd->format(\DateTime::ATOM);
333      }
334      $description = $event->DESCRIPTION;
335      if($description !== null)
336        $entry['description'] = (string)$description;
337      else
338        $entry['description'] = '';
339      $entry['title'] = (string)$event->summary;
340      $entry['id'] = $uid;
341      return $entry;
342  }
343
344  public function getEventWithUid($uid)
345  {
346      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=".
347                $this->sqlite->quote_string($uid);
348      $res = $this->sqlite->query($query);
349      $row = $this->sqlite->res2row($res);
350      return $row;
351  }
352
353  public function getAllCalendarEvents($calid)
354  {
355      $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=".
356               $this->sqlite->quote_string($calid);
357      $res = $this->sqlite->query($query);
358      $arr = $this->sqlite->res2arr($res);
359      return $arr;
360  }
361
362  public function editCalendarEntryForPage($id, $user, $params)
363  {
364      $settings = $this->getPersonalSettings($user);
365      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
366          $timezone = new \DateTimeZone($settings['timezone']);
367      elseif($settings['timezone'] === 'local')
368          $timezone = new \DateTimeZone($params['detectedtz']);
369      else
370          $timezone = new \DateTimeZone('UTC');
371      $startDate = explode('-', $params['eventfrom']);
372      $startTime = explode(':', $params['eventfromtime']);
373      $endDate = explode('-', $params['eventto']);
374      $endTime = explode(':', $params['eventtotime']);
375      $uid = $params['uid'];
376      $event = $this->getEventWithUid($uid);
377      require_once('vendor/autoload.php');
378      if(!isset($event['calendardata']))
379        return false;
380      $uri = $event['uri'];
381      $calid = $event['calendarid'];
382      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
383      $vevent = $vcal->VEVENT;
384      $vevent->summary = $params['eventname'];
385      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
386      $description = $params['eventdescription'];
387      $vevent->remove('DESCRIPTION');
388      $vevent->remove('DTSTAMP');
389      $vevent->remove('LAST-MODIFIED');
390      $vevent->add('DTSTAMP', $dtStamp);
391      $vevent->add('LAST-MODIFIED', $dtStamp);
392      if($description !== '')
393        $vevent->add('DESCRIPTION', $description);
394      $dtStart = new \DateTime();
395      $dtStart->setTimezone($timezone);
396      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
397      if($params['allday'] != '1')
398        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
399      $dtEnd = new \DateTime();
400      $dtEnd->setTimezone($timezone);
401      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
402      if($params['allday'] != '1')
403        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
404      // According to the VCal spec, we need to add a whole day here
405      if($params['allday'] == '1')
406          $dtEnd->add(new \DateInterval('P1D'));
407      $vevent->remove('DTSTART');
408      $vevent->remove('DTEND');
409      $dtStartEv = $vevent->add('DTSTART', $dtStart);
410      $dtEndEv = $vevent->add('DTEND', $dtEnd);
411      if($params['allday'] == '1')
412      {
413          $dtStartEv['VALUE'] = 'DATE';
414          $dtEndEv['VALUE'] = 'DATE';
415      }
416      $now = new DateTime();
417      $eventStr = $vcal->serialize();
418
419      $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr).
420               ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()).
421               ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()).
422               ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()).
423               ", size=".strlen($eventStr).
424               ", etag=".$this->sqlite->quote_string(md5($eventStr)).
425               " WHERE uid=".$this->sqlite->quote_string($uid);
426      $res = $this->sqlite->query($query);
427      if($res !== false)
428      {
429          $this->updateSyncTokenLog($calid, $uri, 'modified');
430          return true;
431      }
432      return false;
433  }
434
435  public function deleteCalendarEntryForPage($id, $params)
436  {
437      $uid = $params['uid'];
438      $event = $this->getEventWithUid($uid);
439      $calid = $event['calendarid'];
440      $uri = $event['uri'];
441      $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid);
442      $res = $this->sqlite->query($query);
443      if($res !== false)
444      {
445          $this->updateSyncTokenLog($calid, $uri, 'deleted');
446      }
447      return true;
448  }
449
450  public function getSyncTokenForCalendar($calid)
451  {
452      $row = $this->getCalendarSettings($calid);
453      if(isset($row['synctoken']))
454          return $row['synctoken'];
455      return false;
456  }
457
458  public function operationNameToOperation($operationName)
459  {
460      switch($operationName)
461      {
462          case 'added':
463              return 1;
464          break;
465          case 'modified':
466              return 2;
467          break;
468          case 'deleted':
469              return 3;
470          break;
471      }
472      return false;
473  }
474
475  private function updateSyncTokenLog($calid, $uri, $operation)
476  {
477      $currentToken = $this->getSyncTokenForCalendar($calid);
478      $operationCode = $this->operationNameToOperation($operation);
479      if(($operationCode === false) || ($currentToken === false))
480          return false;
481      $values = array($uri,
482                      $currentToken,
483                      $calid,
484                      $operationCode
485      );
486      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(".
487               $this->sqlite->quote_and_join($values, ',').")";
488      $res = $this->sqlite->query($query);
489      if($res === false)
490        return false;
491      $currentToken++;
492      $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=".
493               $this->sqlite->quote_string($calid);
494      $res = $this->sqlite->query($query);
495      return ($res !== false);
496  }
497
498  public function getSyncUrlForPage($id, $user = null)
499  {
500      if(is_null($user))
501        $user = $_SERVER['REMOTE_USER'];
502
503      $calid = $this->getCalendarIdForPage($id);
504      if($calid === false)
505        return false;
506
507      $calsettings = $this->getCalendarSettings($calid);
508      if(!isset($calsettings['uri']))
509        return false;
510
511      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
512      return $syncurl;
513  }
514
515  public function getPrivateURLForPage($id)
516  {
517      $calid = $this->getCalendarIdForPage($id);
518      if($calid === false)
519        return false;
520
521      return $this->getPrivateURLForCalendar($calid);
522  }
523
524  public function getPrivateURLForCalendar($calid)
525  {
526      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid);
527      $res = $this->sqlite->query($query);
528      $row = $this->sqlite->res2row($res);
529      if(!isset($row['url']))
530      {
531          $url = uniqid("dokuwiki-").".ics";
532          $values = array(
533                $url,
534                $calid
535          );
536          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(".
537                $this->sqlite->quote_and_join($values, ", ").")";
538          $res = $this->sqlite->query($query);
539          if($res === false)
540            return false;
541      }
542      else
543      {
544          $url = $row['url'];
545      }
546      return DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
547  }
548
549  public function getCalendarForPrivateURL($url)
550  {
551      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url);
552      $res = $this->sqlite->query($query);
553      $row = $this->sqlite->res2row($res);
554      if(!isset($row['calid']))
555        return false;
556      return $row['calid'];
557  }
558
559  public function getCalendarAsICSFeed($calid)
560  {
561      $calSettings = $this->getCalendarSettings($calid);
562      if($calSettings === false)
563        return false;
564      $events = $this->getAllCalendarEvents($calid);
565      if($events === false)
566        return false;
567
568      $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:";
569      $out .= $calSettings['displayname']."\n";
570      foreach($events as $event)
571      {
572          $out .= rtrim($event['calendardata']);
573          $out .= "\n";
574      }
575      $out .= "END:VCALENDAR\n";
576      return $out;
577  }
578
579}
580