xref: /plugin/davcal/helper.php (revision b269830cf3e89c42b8ecae39a10754fa0b08d2ca)
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      else
186          $timezone = new \DateTimeZone('UTC');
187      $startDate = explode('-', $params['eventfrom']);
188      $startTime = explode(':', $params['eventfromtime']);
189      $endDate = explode('-', $params['eventto']);
190      $endTime = explode(':', $params['eventtotime']);
191      require_once('vendor/autoload.php');
192      $vcalendar = new \Sabre\VObject\Component\VCalendar();
193      $event = $vcalendar->add('VEVENT');
194      $uuid = \Sabre\VObject\UUIDUtil::getUUID();
195      $event->add('UID', $uuid);
196      $event->summary = $params['eventname'];
197      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
198      $event->add('DTSTAMP', $dtStamp);
199      $event->add('CREATED', $dtStamp);
200      $event->add('LAST-MODIFIED', $dtStamp);
201      $dtStart = new \DateTime();
202      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
203      if($params['allday'] != '1')
204        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
205      $dtStart->setTimezone($timezone);
206      $dtEnd = new \DateTime();
207      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
208      if($params['allday'] != '1')
209        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
210      $dtEnd->setTimezone($timezone);
211      // According to the VCal spec, we need to add a whole day here
212      if($params['allday'] == '1')
213          $dtEnd->add(new \DateInterval('P1D'));
214      $dtStartEv = $event->add('DTSTART', $dtStart);
215      $dtEndEv = $event->add('DTEND', $dtEnd);
216      if($params['allday'] == '1')
217      {
218          $dtStartEv['VALUE'] = 'DATE';
219          $dtEndEv['VALUE'] = 'DATE';
220      }
221      $calid = $this->getCalendarIdForPage($id);
222      $uri = uniqid('dokuwiki-').'.ics';
223      $now = new DateTime();
224      $eventStr = $vcalendar->serialize();
225
226      $values = array($calid,
227                      $uri,
228                      $eventStr,
229                      $now->getTimestamp(),
230                      'VEVENT',
231                      $event->DTSTART->getDateTime()->getTimeStamp(),
232                      $event->DTEND->getDateTime()->getTimeStamp(),
233                      strlen($eventStr),
234                      md5($eventStr),
235                      uniqid()
236      );
237
238      $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (".$this->sqlite->quote_and_join($values, ',').")";
239      $res = $this->sqlite->query($query);
240      if($res !== false)
241      {
242          $this->updateSyncTokenLog($calid, $uri, 'added');
243          return true;
244      }
245      return false;
246  }
247
248  public function getCalendarSettings($calid)
249  {
250      $query = "SELECT principaluri, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id=".$this->sqlite->quote_string($calid);
251      $res = $this->sqlite->query($query);
252      $row = $this->sqlite->res2row($res);
253      return $row;
254  }
255
256  public function getEventsWithinDateRange($id, $user, $startDate, $endDate)
257  {
258      $settings = $this->getPersonalSettings($user);
259      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
260          $timezone = new \DateTimeZone($settings['timezone']);
261      else
262          $timezone = new \DateTimeZone('UTC');
263      $data = array();
264      require_once('vendor/autoload.php');
265      $calid = $this->getCalendarIdForPage($id);
266      $startTs = new \DateTime($startDate);
267      $endTs = new \DateTime($endDate);
268      $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE (calendarid=".
269                $this->sqlite->quote_string($calid)." AND firstoccurence > ".
270                $this->sqlite->quote_string($startTs->getTimestamp())." AND firstoccurence < ".
271                $this->sqlite->quote_string($endTs->getTimestamp()).") OR (calendarid=".
272                $this->sqlite->quote_string($calid)." AND lastoccurence > ".
273                $this->sqlite->quote_string($startTs->getTimestamp())." AND lastoccurence < ".
274                $this->sqlite->quote_string($endTs->getTimestamp()).")";
275      $res = $this->sqlite->query($query);
276      $arr = $this->sqlite->res2arr($res);
277      foreach($arr as $row)
278      {
279          if(isset($row['calendardata']))
280          {
281              $entry = array();
282              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
283              $start = $vcal->VEVENT->DTSTART;
284              if($start !== null)
285              {
286                $dtStart = $start->getDateTime();
287                $dtStart->setTimezone($timezone);
288                $entry['start'] = $dtStart->format(\DateTime::ATOM);
289                if($start['VALUE'] == 'DATE')
290                  $entry['allDay'] = true;
291                else
292                  $entry['allDay'] = false;
293              }
294              $end = $vcal->VEVENT->DTEND;
295              if($end !== null)
296              {
297                $dtEnd = $end->getDateTime();
298                $dtEnd->setTimezone($timezone);
299                // Subtract the plus one day that was added earlier
300                //if($entry['allDay'] === true)
301                //  $dtEnd->sub(new \DateInterval('P1D'));
302                $entry['end'] = $dtEnd->format(\DateTime::ATOM);
303              }
304              $entry['title'] = (string)$vcal->VEVENT->summary;
305              $entry['id'] = $row['uid'];
306              $data[] = $entry;
307          }
308      }
309      return $data;
310  }
311
312  public function getEventWithUid($uid)
313  {
314      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid=".
315                $this->sqlite->quote_string($uid);
316      $res = $this->sqlite->query($query);
317      $row = $this->sqlite->res2row($res);
318      return $row;
319  }
320
321  public function editCalendarEntryForPage($id, $user, $params)
322  {
323      $settings = $this->getPersonalSettings($user);
324      if($settings['timezone'] !== '' && $settings['timezone'] !== 'local')
325          $timezone = new \DateTimeZone($settings['timezone']);
326      else
327          $timezone = new \DateTimeZone('UTC');
328      $startDate = explode('-', $params['eventfrom']);
329      $startTime = explode(':', $params['eventfromtime']);
330      $endDate = explode('-', $params['eventto']);
331      $endTime = explode(':', $params['eventtotime']);
332      $uid = $params['uid'];
333      $event = $this->getEventWithUid($uid);
334      require_once('vendor/autoload.php');
335      if(!isset($event['calendardata']))
336        return false;
337      $uri = $event['uri'];
338      $calid = $event['calendarid'];
339      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
340      $vevent = $vcal->VEVENT;
341      $vevent->summary = $params['eventname'];
342      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
343      $vevent->remove('DTSTAMP');
344      $vevent->remove('LAST-MODIFIED');
345      $vevent->add('DTSTAMP', $dtStamp);
346      $vevent->add('LAST-MODIFIED', $dtStamp);
347      $dtStart = new \DateTime();
348      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
349      if($params['allday'] != '1')
350        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
351      $dtStart->setTimezone($timezone);
352      $dtEnd = new \DateTime();
353      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
354      if($params['allday'] != '1')
355        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
356      $dtEnd->setTimezone($timezone);
357      // According to the VCal spec, we need to add a whole day here
358      if($params['allday'] == '1')
359          $dtEnd->add(new \DateInterval('P1D'));
360      $vevent->remove('DTSTART');
361      $vevent->remove('DTEND');
362      $dtStartEv = $vevent->add('DTSTART', $dtStart);
363      $dtEndEv = $vevent->add('DTEND', $dtEnd);
364      if($params['allday'] == '1')
365      {
366          $dtStartEv['VALUE'] = 'DATE';
367          $dtEndEv['VALUE'] = 'DATE';
368      }
369      $now = new DateTime();
370      $eventStr = $vcal->serialize();
371
372      $query = "UPDATE calendarobjects SET calendardata=".$this->sqlite->quote_string($eventStr).
373               ", lastmodified=".$this->sqlite->quote_string($now->getTimestamp()).
374               ", firstoccurence=".$this->sqlite->quote_string($dtStart->getTimestamp()).
375               ", lastoccurence=".$this->sqlite->quote_string($dtEnd->getTimestamp()).
376               ", size=".strlen($eventStr).
377               ", etag=".$this->sqlite->quote_string(md5($eventStr)).
378               " WHERE uid=".$this->sqlite->quote_string($uid);
379      $res = $this->sqlite->query($query);
380      if($res !== false)
381      {
382          $this->updateSyncTokenLog($calid, $uri, 'modified');
383          return true;
384      }
385      return false;
386  }
387
388  public function deleteCalendarEntryForPage($id, $params)
389  {
390      $uid = $params['uid'];
391      $event = $this->getEventWithUid($uid);
392      $calid = $event['calendarid'];
393      $uri = $event['uri'];
394      $query = "DELETE FROM calendarobjects WHERE uid=".$this->sqlite->quote_string($uid);
395      $res = $this->sqlite->query($query);
396      if($res !== false)
397      {
398          $this->updateSyncTokenLog($calid, $uri, 'deleted');
399      }
400      return true;
401  }
402
403  public function getSyncTokenForCalendar($calid)
404  {
405      $row = $this->getCalendarSettings($calid);
406      if(isset($row['synctoken']))
407          return $row['synctoken'];
408      return false;
409  }
410
411  public function operationNameToOperation($operationName)
412  {
413      switch($operationName)
414      {
415          case 'added':
416              return 1;
417          break;
418          case 'modified':
419              return 2;
420          break;
421          case 'deleted':
422              return 3;
423          break;
424      }
425      return false;
426  }
427
428  private function updateSyncTokenLog($calid, $uri, $operation)
429  {
430      $currentToken = $this->getSyncTokenForCalendar($calid);
431      $operationCode = $this->operationNameToOperation($operation);
432      if(($operationCode === false) || ($currentToken === false))
433          return false;
434      $values = array($uri,
435                      $currentToken,
436                      $calid,
437                      $operationCode
438      );
439      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(".
440               $this->sqlite->quote_and_join($values, ',').")";
441      $res = $this->sqlite->query($query);
442      if($res === false)
443        return false;
444      $currentToken++;
445      $query = "UPDATE calendars SET synctoken=".$this->sqlite->quote_string($currentToken)." WHERE id=".
446               $this->sqlite->quote_string($calid);
447      $res = $this->sqlite->query($query);
448      return ($res !== false);
449  }
450
451  public function getSyncUrlForPage($id, $user = null)
452  {
453      if(is_null($user))
454        $user = $_SERVER['REMOTE_USER'];
455
456      $calid = $this->getCalendarIdForPage($id);
457      if($calid === false)
458        return false;
459
460      $calsettings = $this->getCalendarSettings($calid);
461      if(!isset($calsettings['uri']))
462        return false;
463
464      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
465      return $syncurl;
466  }
467
468}
469