xref: /plugin/davcal/helper.php (revision a25c89ea879751dde5265d6617cd18f4dcfe63f7)
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 editCalendarEntryForPage($id, $user, $params)
329  {
330      $settings = $this->getPersonalSettings($user);
331      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
332          $timezone = new \DateTimeZone($settings['timezone']);
333      elseif($settings['timezone'] === 'local')
334          $timezone = new \DateTimeZone($params['detectedtz']);
335      else
336          $timezone = new \DateTimeZone('UTC');
337      $startDate = explode('-', $params['eventfrom']);
338      $startTime = explode(':', $params['eventfromtime']);
339      $endDate = explode('-', $params['eventto']);
340      $endTime = explode(':', $params['eventtotime']);
341      $uid = $params['uid'];
342      $event = $this->getEventWithUid($uid);
343      require_once('vendor/autoload.php');
344      if(!isset($event['calendardata']))
345        return false;
346      $uri = $event['uri'];
347      $calid = $event['calendarid'];
348      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
349      $vevent = $vcal->VEVENT;
350      $vevent->summary = $params['eventname'];
351      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
352      $description = $params['eventdescription'];
353      $vevent->remove('DESCRIPTION');
354      $vevent->remove('DTSTAMP');
355      $vevent->remove('LAST-MODIFIED');
356      $vevent->add('DTSTAMP', $dtStamp);
357      $vevent->add('LAST-MODIFIED', $dtStamp);
358      if($description !== '')
359        $vevent->add('DESCRIPTION', $description);
360      $dtStart = new \DateTime();
361      $dtStart->setTimezone($timezone);
362      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
363      if($params['allday'] != '1')
364        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
365      $dtEnd = new \DateTime();
366      $dtEnd->setTimezone($timezone);
367      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
368      if($params['allday'] != '1')
369        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
370      // According to the VCal spec, we need to add a whole day here
371      if($params['allday'] == '1')
372          $dtEnd->add(new \DateInterval('P1D'));
373      $vevent->remove('DTSTART');
374      $vevent->remove('DTEND');
375      $dtStartEv = $vevent->add('DTSTART', $dtStart);
376      $dtEndEv = $vevent->add('DTEND', $dtEnd);
377      if($params['allday'] == '1')
378      {
379          $dtStartEv['VALUE'] = 'DATE';
380          $dtEndEv['VALUE'] = 'DATE';
381      }
382      $now = new DateTime();
383      $eventStr = $vcal->serialize();
384
385      $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr).
386               ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()).
387               ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()).
388               ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()).
389               ", size=".strlen($eventStr).
390               ", etag=".$this->sqlite->quote_string(md5($eventStr)).
391               " WHERE uid=".$this->sqlite->quote_string($uid);
392      $res = $this->sqlite->query($query);
393      if($res !== false)
394      {
395          $this->updateSyncTokenLog($calid, $uri, 'modified');
396          return true;
397      }
398      return false;
399  }
400
401  public function deleteCalendarEntryForPage($id, $params)
402  {
403      $uid = $params['uid'];
404      $event = $this->getEventWithUid($uid);
405      $calid = $event['calendarid'];
406      $uri = $event['uri'];
407      $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid);
408      $res = $this->sqlite->query($query);
409      if($res !== false)
410      {
411          $this->updateSyncTokenLog($calid, $uri, 'deleted');
412      }
413      return true;
414  }
415
416  public function getSyncTokenForCalendar($calid)
417  {
418      $row = $this->getCalendarSettings($calid);
419      if(isset($row['synctoken']))
420          return $row['synctoken'];
421      return false;
422  }
423
424  public function operationNameToOperation($operationName)
425  {
426      switch($operationName)
427      {
428          case 'added':
429              return 1;
430          break;
431          case 'modified':
432              return 2;
433          break;
434          case 'deleted':
435              return 3;
436          break;
437      }
438      return false;
439  }
440
441  private function updateSyncTokenLog($calid, $uri, $operation)
442  {
443      $currentToken = $this->getSyncTokenForCalendar($calid);
444      $operationCode = $this->operationNameToOperation($operation);
445      if(($operationCode === false) || ($currentToken === false))
446          return false;
447      $values = array($uri,
448                      $currentToken,
449                      $calid,
450                      $operationCode
451      );
452      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(".
453               $this->sqlite->quote_and_join($values, ',').")";
454      $res = $this->sqlite->query($query);
455      if($res === false)
456        return false;
457      $currentToken++;
458      $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=".
459               $this->sqlite->quote_string($calid);
460      $res = $this->sqlite->query($query);
461      return ($res !== false);
462  }
463
464  public function getSyncUrlForPage($id, $user = null)
465  {
466      if(is_null($user))
467        $user = $_SERVER['REMOTE_USER'];
468
469      $calid = $this->getCalendarIdForPage($id);
470      if($calid === false)
471        return false;
472
473      $calsettings = $this->getCalendarSettings($calid);
474      if(!isset($calsettings['uri']))
475        return false;
476
477      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
478      return $syncurl;
479  }
480
481}
482