xref: /plugin/davcal/helper.php (revision f69bb4491e1a8930c2218737f8703c41c7507247)
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($startTs->getTimestamp())." AND firstoccurence < ".
276                $this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=".
277                $this->sqlite->quote_string($calid)." AND lastoccurence > ".
278                $this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ".
279                $this->sqlite->quote_string($endTs->getTimestamp()).")";
280      $res = $this->sqlite->query($query);
281      $arr = $this->sqlite->res2arr($res);
282      foreach($arr as $row)
283      {
284          if(isset($row['calendardata']))
285          {
286              $entry = array();
287              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
288              $start = $vcal->VEVENT->DTSTART;
289              if($start !== null)
290              {
291                $dtStart = $start->getDateTime();
292                $dtStart->setTimezone($timezone);
293                $entry['start'] = $dtStart->format(\DateTime::ATOM);
294                if($start['VALUE'] == 'DATE')
295                  $entry['allDay'] = true;
296                else
297                  $entry['allDay'] = false;
298              }
299              $end = $vcal->VEVENT->DTEND;
300              if($end !== null)
301              {
302                $dtEnd = $end->getDateTime();
303                $dtEnd->setTimezone($timezone);
304                $entry['end'] = $dtEnd->format(\DateTime::ATOM);
305              }
306              $description = $vcal->VEVENT->DESCRIPTION;
307              if($description !== null)
308                $entry['description'] = (string)$description;
309              else
310                $entry['description'] = '';
311              $entry['title'] = (string)$vcal->VEVENT->summary;
312              $entry['id'] = $row['uid'];
313              $data[] = $entry;
314          }
315      }
316      return $data;
317  }
318
319  public function getEventWithUid($uid)
320  {
321      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=".
322                $this->sqlite->quote_string($uid);
323      $res = $this->sqlite->query($query);
324      $row = $this->sqlite->res2row($res);
325      return $row;
326  }
327
328  public function getAllCalendarEvents($calid)
329  {
330      $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid=".
331               $this->sqlite->quote_string($calid);
332      $res = $this->sqlite->query($query);
333      $arr = $this->sqlite->res2arr($res);
334      return $arr;
335  }
336
337  public function editCalendarEntryForPage($id, $user, $params)
338  {
339      $settings = $this->getPersonalSettings($user);
340      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
341          $timezone = new \DateTimeZone($settings['timezone']);
342      elseif($settings['timezone'] === 'local')
343          $timezone = new \DateTimeZone($params['detectedtz']);
344      else
345          $timezone = new \DateTimeZone('UTC');
346      $startDate = explode('-', $params['eventfrom']);
347      $startTime = explode(':', $params['eventfromtime']);
348      $endDate = explode('-', $params['eventto']);
349      $endTime = explode(':', $params['eventtotime']);
350      $uid = $params['uid'];
351      $event = $this->getEventWithUid($uid);
352      require_once('vendor/autoload.php');
353      if(!isset($event['calendardata']))
354        return false;
355      $uri = $event['uri'];
356      $calid = $event['calendarid'];
357      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
358      $vevent = $vcal->VEVENT;
359      $vevent->summary = $params['eventname'];
360      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
361      $description = $params['eventdescription'];
362      $vevent->remove('DESCRIPTION');
363      $vevent->remove('DTSTAMP');
364      $vevent->remove('LAST-MODIFIED');
365      $vevent->add('DTSTAMP', $dtStamp);
366      $vevent->add('LAST-MODIFIED', $dtStamp);
367      if($description !== '')
368        $vevent->add('DESCRIPTION', $description);
369      $dtStart = new \DateTime();
370      $dtStart->setTimezone($timezone);
371      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
372      if($params['allday'] != '1')
373        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
374      $dtEnd = new \DateTime();
375      $dtEnd->setTimezone($timezone);
376      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
377      if($params['allday'] != '1')
378        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
379      // According to the VCal spec, we need to add a whole day here
380      if($params['allday'] == '1')
381          $dtEnd->add(new \DateInterval('P1D'));
382      $vevent->remove('DTSTART');
383      $vevent->remove('DTEND');
384      $dtStartEv = $vevent->add('DTSTART', $dtStart);
385      $dtEndEv = $vevent->add('DTEND', $dtEnd);
386      if($params['allday'] == '1')
387      {
388          $dtStartEv['VALUE'] = 'DATE';
389          $dtEndEv['VALUE'] = 'DATE';
390      }
391      $now = new DateTime();
392      $eventStr = $vcal->serialize();
393
394      $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr).
395               ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()).
396               ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()).
397               ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()).
398               ", size=".strlen($eventStr).
399               ", etag=".$this->sqlite->quote_string(md5($eventStr)).
400               " WHERE uid=".$this->sqlite->quote_string($uid);
401      $res = $this->sqlite->query($query);
402      if($res !== false)
403      {
404          $this->updateSyncTokenLog($calid, $uri, 'modified');
405          return true;
406      }
407      return false;
408  }
409
410  public function deleteCalendarEntryForPage($id, $params)
411  {
412      $uid = $params['uid'];
413      $event = $this->getEventWithUid($uid);
414      $calid = $event['calendarid'];
415      $uri = $event['uri'];
416      $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid);
417      $res = $this->sqlite->query($query);
418      if($res !== false)
419      {
420          $this->updateSyncTokenLog($calid, $uri, 'deleted');
421      }
422      return true;
423  }
424
425  public function getSyncTokenForCalendar($calid)
426  {
427      $row = $this->getCalendarSettings($calid);
428      if(isset($row['synctoken']))
429          return $row['synctoken'];
430      return false;
431  }
432
433  public function operationNameToOperation($operationName)
434  {
435      switch($operationName)
436      {
437          case 'added':
438              return 1;
439          break;
440          case 'modified':
441              return 2;
442          break;
443          case 'deleted':
444              return 3;
445          break;
446      }
447      return false;
448  }
449
450  private function updateSyncTokenLog($calid, $uri, $operation)
451  {
452      $currentToken = $this->getSyncTokenForCalendar($calid);
453      $operationCode = $this->operationNameToOperation($operation);
454      if(($operationCode === false) || ($currentToken === false))
455          return false;
456      $values = array($uri,
457                      $currentToken,
458                      $calid,
459                      $operationCode
460      );
461      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(".
462               $this->sqlite->quote_and_join($values, ',').")";
463      $res = $this->sqlite->query($query);
464      if($res === false)
465        return false;
466      $currentToken++;
467      $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=".
468               $this->sqlite->quote_string($calid);
469      $res = $this->sqlite->query($query);
470      return ($res !== false);
471  }
472
473  public function getSyncUrlForPage($id, $user = null)
474  {
475      if(is_null($user))
476        $user = $_SERVER['REMOTE_USER'];
477
478      $calid = $this->getCalendarIdForPage($id);
479      if($calid === false)
480        return false;
481
482      $calsettings = $this->getCalendarSettings($calid);
483      if(!isset($calsettings['uri']))
484        return false;
485
486      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
487      return $syncurl;
488  }
489
490  public function getPrivateURLForPage($id)
491  {
492      $calid = $this->getCalendarIdForPage($id);
493      if($calid === false)
494        return false;
495
496      return $this->getPrivateURLForCalendar($calid);
497  }
498
499  public function getPrivateURLForCalendar($calid)
500  {
501      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid=".$this->sqlite->quote_string($calid);
502      $res = $this->sqlite->query($query);
503      $row = $this->sqlite->res2row($res);
504      if(!isset($row['url']))
505      {
506          $url = uniqid("dokuwiki-").".ics";
507          $values = array(
508                $url,
509                $calid
510          );
511          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(".
512                $this->sqlite->quote_and_join($values, ", ").")";
513          $res = $this->sqlite->query($query);
514          if($res === false)
515            return false;
516      }
517      else
518      {
519          $url = $row['url'];
520      }
521      return DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$url;
522  }
523
524  public function getCalendarForPrivateURL($url)
525  {
526      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url=".$this->sqlite->quote_string($url);
527      $res = $this->sqlite->query($query);
528      $row = $this->sqlite->res2row($res);
529      if(!isset($row['calid']))
530        return false;
531      return $row['calid'];
532  }
533
534  public function getCalendarAsICSFeed($calid)
535  {
536      $calSettings = $this->getCalendarSettings($calid);
537      if($calSettings === false)
538        return false;
539      $events = $this->getAllCalendarEvents($calid);
540      if($events === false)
541        return false;
542
543      $out = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\nCALSCALE:GREGORIAN\nX-WR-CALNAME:";
544      $out .= $calSettings['displayname']."\n";
545      foreach($events as $event)
546      {
547          $out .= rtrim($event['calendardata']);
548          $out .= "\n";
549      }
550      $out .= "END:VCALENDAR\n";
551      return $out;
552  }
553
554}
555