xref: /plugin/davcal/helper.php (revision d5703f5ab52f470e8a65f18aad4b649cbb5febeb)
1a1a3b679SAndreas Boehler<?php
2a1a3b679SAndreas Boehler/**
3cb71a62aSAndreas Boehler  * Helper Class for the DAVCal plugin
4a1a3b679SAndreas Boehler  * This helper does the actual work.
5a1a3b679SAndreas Boehler  *
6a1a3b679SAndreas Boehler  */
7a1a3b679SAndreas Boehler
8a1a3b679SAndreas Boehler// must be run within Dokuwiki
9a1a3b679SAndreas Boehlerif(!defined('DOKU_INC')) die();
10a1a3b679SAndreas Boehler
11a1a3b679SAndreas Boehlerclass helper_plugin_davcal extends DokuWiki_Plugin {
12a1a3b679SAndreas Boehler
13a1a3b679SAndreas Boehler  protected $sqlite = null;
14185e2535SAndreas Boehler  protected $cachedValues = array();
15a1a3b679SAndreas Boehler
16a1a3b679SAndreas Boehler  /**
17cb71a62aSAndreas Boehler    * Constructor to load the configuration and the SQLite plugin
18a1a3b679SAndreas Boehler    */
19a1a3b679SAndreas Boehler  public function helper_plugin_davcal() {
20a1a3b679SAndreas Boehler    $this->sqlite =& plugin_load('helper', 'sqlite');
2121d04f73SAndreas Boehler    global $conf;
2221d04f73SAndreas Boehler    if($conf['allowdebug'])
2321d04f73SAndreas Boehler        dbglog('---- DAVCAL helper.php init');
24a1a3b679SAndreas Boehler    if(!$this->sqlite)
25a1a3b679SAndreas Boehler    {
2621d04f73SAndreas Boehler        if($conf['allowdebug'])
2721d04f73SAndreas Boehler            dbglog('This plugin requires the sqlite plugin. Please install it.');
28a1a3b679SAndreas Boehler        msg('This plugin requires the sqlite plugin. Please install it.');
29a1a3b679SAndreas Boehler        return;
30a1a3b679SAndreas Boehler    }
31a1a3b679SAndreas Boehler
32a1a3b679SAndreas Boehler    if(!$this->sqlite->init('davcal', DOKU_PLUGIN.'davcal/db/'))
33a1a3b679SAndreas Boehler    {
3421d04f73SAndreas Boehler        if($conf['allowdebug'])
3521d04f73SAndreas Boehler            dbglog('Error initialising the SQLite DB for DAVCal');
36a1a3b679SAndreas Boehler        return;
37a1a3b679SAndreas Boehler    }
38a1a3b679SAndreas Boehler  }
39a1a3b679SAndreas Boehler
40cb71a62aSAndreas Boehler  /**
41185e2535SAndreas Boehler   * Retrieve meta data for a given page
42185e2535SAndreas Boehler   *
43185e2535SAndreas Boehler   * @param string $id optional The page ID
44185e2535SAndreas Boehler   * @return array The metadata
45185e2535SAndreas Boehler   */
46185e2535SAndreas Boehler  private function getMeta($id = null) {
47185e2535SAndreas Boehler    global $ID;
48185e2535SAndreas Boehler    global $INFO;
49185e2535SAndreas Boehler
50185e2535SAndreas Boehler    if ($id === null) $id = $ID;
51185e2535SAndreas Boehler
52185e2535SAndreas Boehler    if($ID === $id && $INFO['meta']) {
53185e2535SAndreas Boehler        $meta = $INFO['meta'];
54185e2535SAndreas Boehler    } else {
55185e2535SAndreas Boehler        $meta = p_get_metadata($id);
56185e2535SAndreas Boehler    }
57185e2535SAndreas Boehler
58185e2535SAndreas Boehler    return $meta;
59185e2535SAndreas Boehler  }
60185e2535SAndreas Boehler
61185e2535SAndreas Boehler  /**
62185e2535SAndreas Boehler   * Retrieve the meta data for a given page
63185e2535SAndreas Boehler   *
64185e2535SAndreas Boehler   * @param string $id optional The page ID
65185e2535SAndreas Boehler   * @return array with meta data
66185e2535SAndreas Boehler   */
67185e2535SAndreas Boehler  public function getCalendarMetaForPage($id = null)
68185e2535SAndreas Boehler  {
69185e2535SAndreas Boehler      if(is_null($id))
70185e2535SAndreas Boehler      {
71185e2535SAndreas Boehler          global $ID;
72185e2535SAndreas Boehler          $id = $ID;
73185e2535SAndreas Boehler      }
74185e2535SAndreas Boehler
75185e2535SAndreas Boehler      $meta = $this->getMeta($id);
76185e2535SAndreas Boehler      if(isset($meta['plugin_davcal']))
77185e2535SAndreas Boehler        return $meta['plugin_davcal'];
78185e2535SAndreas Boehler      else
79185e2535SAndreas Boehler        return array();
80185e2535SAndreas Boehler  }
81185e2535SAndreas Boehler
82185e2535SAndreas Boehler  /**
8380e1ddf7SAndreas Boehler   * Filter calendar pages and return only those where the current
8480e1ddf7SAndreas Boehler   * user has at least read permission.
8580e1ddf7SAndreas Boehler   *
8680e1ddf7SAndreas Boehler   * @param array $calendarPages Array with calendar pages to check
8780e1ddf7SAndreas Boehler   * @return array with filtered calendar pages
8880e1ddf7SAndreas Boehler   */
8980e1ddf7SAndreas Boehler  public function filterCalendarPagesByUserPermission($calendarPages)
9080e1ddf7SAndreas Boehler  {
9180e1ddf7SAndreas Boehler      $retList = array();
9280e1ddf7SAndreas Boehler      foreach($calendarPages as $page => $data)
9380e1ddf7SAndreas Boehler      {
9480e1ddf7SAndreas Boehler          if(auth_quickaclcheck($page) >= AUTH_READ)
9580e1ddf7SAndreas Boehler          {
9680e1ddf7SAndreas Boehler              $retList[$page] = $data;
9780e1ddf7SAndreas Boehler          }
9880e1ddf7SAndreas Boehler      }
9980e1ddf7SAndreas Boehler      return $retList;
10080e1ddf7SAndreas Boehler  }
10180e1ddf7SAndreas Boehler
10280e1ddf7SAndreas Boehler  /**
103185e2535SAndreas Boehler   * Get all calendar pages used by a given page
104185e2535SAndreas Boehler   * based on the stored metadata
105185e2535SAndreas Boehler   *
106185e2535SAndreas Boehler   * @param string $id optional The page id
107185e2535SAndreas Boehler   * @return mixed The pages as array or false
108185e2535SAndreas Boehler   */
109185e2535SAndreas Boehler  public function getCalendarPagesByMeta($id = null)
110185e2535SAndreas Boehler  {
111185e2535SAndreas Boehler      if(is_null($id))
112185e2535SAndreas Boehler      {
113185e2535SAndreas Boehler          global $ID;
114185e2535SAndreas Boehler          $id = $ID;
115185e2535SAndreas Boehler      }
116185e2535SAndreas Boehler
117185e2535SAndreas Boehler      $meta = $this->getCalendarMetaForPage($id);
118185e2535SAndreas Boehler      if(isset($meta['id']))
119ed764890SAndreas Boehler      {
120ed764890SAndreas Boehler          // Filter the list of pages by permission
12180e1ddf7SAndreas Boehler          $pages = $this->filterCalendarPagesByUserPermission($meta['id']);
12280e1ddf7SAndreas Boehler          if(empty($pages))
123ed764890SAndreas Boehler            return false;
12480e1ddf7SAndreas Boehler          return $pages;
125ed764890SAndreas Boehler      }
126185e2535SAndreas Boehler      return false;
127185e2535SAndreas Boehler  }
128185e2535SAndreas Boehler
129185e2535SAndreas Boehler  /**
130185e2535SAndreas Boehler   * Get a list of calendar names/pages/ids/colors
131185e2535SAndreas Boehler   * for an array of page ids
132185e2535SAndreas Boehler   *
133185e2535SAndreas Boehler   * @param array $calendarPages The calendar pages to retrieve
134185e2535SAndreas Boehler   * @return array The list
135185e2535SAndreas Boehler   */
136185e2535SAndreas Boehler  public function getCalendarMapForIDs($calendarPages)
137185e2535SAndreas Boehler  {
138185e2535SAndreas Boehler      $data = array();
1394a2bf5eeSAndreas Boehler      foreach($calendarPages as $page => $color)
140185e2535SAndreas Boehler      {
141185e2535SAndreas Boehler          $calid = $this->getCalendarIdForPage($page);
142185e2535SAndreas Boehler          if($calid !== false)
143185e2535SAndreas Boehler          {
144185e2535SAndreas Boehler            $settings = $this->getCalendarSettings($calid);
145185e2535SAndreas Boehler            $name = $settings['displayname'];
146ed764890SAndreas Boehler            $write = (auth_quickaclcheck($page) > AUTH_READ);
147185e2535SAndreas Boehler            $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid,
148ed764890SAndreas Boehler                            'color' => $color, 'write' => $write);
149185e2535SAndreas Boehler          }
150185e2535SAndreas Boehler      }
151185e2535SAndreas Boehler      return $data;
152185e2535SAndreas Boehler  }
153185e2535SAndreas Boehler
154185e2535SAndreas Boehler  /**
155185e2535SAndreas Boehler   * Get the saved calendar color for a given page.
156185e2535SAndreas Boehler   *
157185e2535SAndreas Boehler   * @param string $id optional The page ID
158185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
159185e2535SAndreas Boehler   */
160185e2535SAndreas Boehler  public function getCalendarColorForPage($id = null)
161185e2535SAndreas Boehler  {
162185e2535SAndreas Boehler      if(is_null($id))
163185e2535SAndreas Boehler      {
164185e2535SAndreas Boehler          global $ID;
165185e2535SAndreas Boehler          $id = $ID;
166185e2535SAndreas Boehler      }
167185e2535SAndreas Boehler
168185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
169185e2535SAndreas Boehler      if($calid === false)
170185e2535SAndreas Boehler        return false;
171185e2535SAndreas Boehler
172185e2535SAndreas Boehler      return $this->getCalendarColorForCalendar($calid);
173185e2535SAndreas Boehler  }
174185e2535SAndreas Boehler
175185e2535SAndreas Boehler  /**
176185e2535SAndreas Boehler   * Get the saved calendar color for a given calendar ID.
177185e2535SAndreas Boehler   *
178185e2535SAndreas Boehler   * @param string $id optional The calendar ID
179185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
180185e2535SAndreas Boehler   */
181185e2535SAndreas Boehler  public function getCalendarColorForCalendar($calid)
182185e2535SAndreas Boehler  {
183185e2535SAndreas Boehler      if(isset($this->cachedValues['calendarcolor'][$calid]))
184185e2535SAndreas Boehler        return $this->cachedValues['calendarcolor'][$calid];
185185e2535SAndreas Boehler
186185e2535SAndreas Boehler      $row = $this->getCalendarSettings($calid);
187185e2535SAndreas Boehler
188185e2535SAndreas Boehler      if(!isset($row['calendarcolor']))
189185e2535SAndreas Boehler        return false;
190185e2535SAndreas Boehler
191185e2535SAndreas Boehler      $color = $row['calendarcolor'];
192185e2535SAndreas Boehler      $this->cachedValues['calendarcolor'][$calid] = $color;
193185e2535SAndreas Boehler      return $color;
194185e2535SAndreas Boehler  }
195185e2535SAndreas Boehler
196185e2535SAndreas Boehler  /**
197e86c8dd3SAndreas Boehler   * Get the user's principal URL for iOS sync
198e86c8dd3SAndreas Boehler   * @param string $user the user name
199e86c8dd3SAndreas Boehler   * @return the URL to the principal sync
200e86c8dd3SAndreas Boehler   */
201e86c8dd3SAndreas Boehler  public function getPrincipalUrlForUser($user)
202e86c8dd3SAndreas Boehler  {
203e86c8dd3SAndreas Boehler      if(is_null($user))
204e86c8dd3SAndreas Boehler        return false;
205e86c8dd3SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user;
206e86c8dd3SAndreas Boehler      return $url;
207e86c8dd3SAndreas Boehler  }
208e86c8dd3SAndreas Boehler
209e86c8dd3SAndreas Boehler  /**
210185e2535SAndreas Boehler   * Set the calendar color for a given page.
211185e2535SAndreas Boehler   *
212185e2535SAndreas Boehler   * @param string $color The color definition
213185e2535SAndreas Boehler   * @param string $id optional The page ID
214185e2535SAndreas Boehler   * @return boolean True on success, otherwise false
215185e2535SAndreas Boehler   */
216185e2535SAndreas Boehler  public function setCalendarColorForPage($color, $id = null)
217185e2535SAndreas Boehler  {
218185e2535SAndreas Boehler      if(is_null($id))
219185e2535SAndreas Boehler      {
220185e2535SAndreas Boehler          global $ID;
221185e2535SAndreas Boehler          $id = $ID;
222185e2535SAndreas Boehler      }
223185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
224185e2535SAndreas Boehler      if($calid === false)
225185e2535SAndreas Boehler        return false;
226185e2535SAndreas Boehler
22751f4febbSAndreas Boehler      $query = "UPDATE calendars SET calendarcolor = ? ".
22851f4febbSAndreas Boehler               " WHERE id = ?";
22951f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $color, $calid);
230185e2535SAndreas Boehler      if($res !== false)
231185e2535SAndreas Boehler      {
232185e2535SAndreas Boehler        $this->cachedValues['calendarcolor'][$calid] = $color;
233185e2535SAndreas Boehler        return true;
234185e2535SAndreas Boehler      }
235185e2535SAndreas Boehler      return false;
236185e2535SAndreas Boehler  }
237185e2535SAndreas Boehler
238185e2535SAndreas Boehler  /**
239cb71a62aSAndreas Boehler   * Set the calendar name and description for a given page with a given
240cb71a62aSAndreas Boehler   * page id.
241cb71a62aSAndreas Boehler   * If the calendar doesn't exist, the calendar is created!
242cb71a62aSAndreas Boehler   *
243cb71a62aSAndreas Boehler   * @param string  $name The name of the new calendar
244cb71a62aSAndreas Boehler   * @param string  $description The description of the new calendar
245cb71a62aSAndreas Boehler   * @param string  $id (optional) The ID of the page
246cb71a62aSAndreas Boehler   * @param string  $userid The userid of the creating user
247cb71a62aSAndreas Boehler   *
248cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false.
249cb71a62aSAndreas Boehler   */
250a1a3b679SAndreas Boehler  public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
251a1a3b679SAndreas Boehler  {
252a1a3b679SAndreas Boehler      if(is_null($id))
253a1a3b679SAndreas Boehler      {
254a1a3b679SAndreas Boehler          global $ID;
255a1a3b679SAndreas Boehler          $id = $ID;
256a1a3b679SAndreas Boehler      }
257a1a3b679SAndreas Boehler      if(is_null($userid))
25834a47953SAndreas Boehler      {
25934a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
26034a47953SAndreas Boehler        {
261a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
26234a47953SAndreas Boehler        }
26334a47953SAndreas Boehler        else
26434a47953SAndreas Boehler        {
26534a47953SAndreas Boehler          $userid = uniqid('davcal-');
26634a47953SAndreas Boehler        }
26734a47953SAndreas Boehler      }
268a1a3b679SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
269a1a3b679SAndreas Boehler      if($calid === false)
270a1a3b679SAndreas Boehler        return $this->createCalendarForPage($name, $description, $id, $userid);
271a1a3b679SAndreas Boehler
27251f4febbSAndreas Boehler      $query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?";
27351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $name, $description, $calid);
274b269830cSAndreas Boehler      if($res !== false)
275b269830cSAndreas Boehler        return true;
276b269830cSAndreas Boehler      return false;
277a1a3b679SAndreas Boehler  }
278a1a3b679SAndreas Boehler
279cb71a62aSAndreas Boehler  /**
280*d5703f5aSAndreas Boehler   * Update a calendar's displayname
281*d5703f5aSAndreas Boehler   *
282*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
283*d5703f5aSAndreas Boehler   * @param string $name The new calendar name
284*d5703f5aSAndreas Boehler   *
285*d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
286*d5703f5aSAndreas Boehler   */
287*d5703f5aSAndreas Boehler  public function updateCalendarName($calid, $name)
288*d5703f5aSAndreas Boehler  {
289*d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET displayname = ? WHERE id = ?";
290*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $name);
291*d5703f5aSAndreas Boehler      if($res !== false)
292*d5703f5aSAndreas Boehler      {
293*d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
294*d5703f5aSAndreas Boehler        return true;
295*d5703f5aSAndreas Boehler      }
296*d5703f5aSAndreas Boehler      return false;
297*d5703f5aSAndreas Boehler  }
298*d5703f5aSAndreas Boehler
299*d5703f5aSAndreas Boehler  /**
300*d5703f5aSAndreas Boehler   * Update the calendar description
301*d5703f5aSAndreas Boehler   *
302*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
303*d5703f5aSAndreas Boehler   * @param string $description The new calendar's description
304*d5703f5aSAndreas Boehler   *
305*d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
306*d5703f5aSAndreas Boehler   */
307*d5703f5aSAndreas Boehler  public function updateCalendarDescription($calid, $description)
308*d5703f5aSAndreas Boehler  {
309*d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET description = ? WHERE id = ?";
310*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $description);
311*d5703f5aSAndreas Boehler      if($res !== false)
312*d5703f5aSAndreas Boehler      {
313*d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
314*d5703f5aSAndreas Boehler        return true;
315*d5703f5aSAndreas Boehler      }
316*d5703f5aSAndreas Boehler      return false;
317*d5703f5aSAndreas Boehler  }
318*d5703f5aSAndreas Boehler
319*d5703f5aSAndreas Boehler  /**
320*d5703f5aSAndreas Boehler   * Update a calendar's timezone information
321*d5703f5aSAndreas Boehler   *
322*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
323*d5703f5aSAndreas Boehler   * @param string $timezone The new timezone to set
324*d5703f5aSAndreas Boehler   *
325*d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
326*d5703f5aSAndreas Boehler   */
327*d5703f5aSAndreas Boehler  public function updateCalendarTimezone($calid, $timezone)
328*d5703f5aSAndreas Boehler  {
329*d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET timezone = ? WHERE id = ?";
330*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $timezone);
331*d5703f5aSAndreas Boehler      if($res !== false)
332*d5703f5aSAndreas Boehler      {
333*d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
334*d5703f5aSAndreas Boehler        return true;
335*d5703f5aSAndreas Boehler      }
336*d5703f5aSAndreas Boehler      return false;
337*d5703f5aSAndreas Boehler  }
338*d5703f5aSAndreas Boehler
339*d5703f5aSAndreas Boehler  /**
340cb71a62aSAndreas Boehler   * Save the personal settings to the SQLite database 'calendarsettings'.
341cb71a62aSAndreas Boehler   *
342cb71a62aSAndreas Boehler   * @param array  $settings The settings array to store
343cb71a62aSAndreas Boehler   * @param string $userid (optional) The userid to store
344cb71a62aSAndreas Boehler   *
345cb71a62aSAndreas Boehler   * @param boolean True on success, otherwise false
346cb71a62aSAndreas Boehler   */
347a495d34cSAndreas Boehler  public function savePersonalSettings($settings, $userid = null)
348a495d34cSAndreas Boehler  {
349a495d34cSAndreas Boehler      if(is_null($userid))
35034a47953SAndreas Boehler      {
35134a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
35234a47953SAndreas Boehler          {
353a495d34cSAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
35434a47953SAndreas Boehler          }
35534a47953SAndreas Boehler          else
35634a47953SAndreas Boehler          {
35734a47953SAndreas Boehler              return false;
35834a47953SAndreas Boehler          }
35934a47953SAndreas Boehler      }
360a495d34cSAndreas Boehler      $this->sqlite->query("BEGIN TRANSACTION");
361a495d34cSAndreas Boehler
36251f4febbSAndreas Boehler      $query = "DELETE FROM calendarsettings WHERE userid = ?";
36351f4febbSAndreas Boehler      $this->sqlite->query($query, $userid);
364bd883736SAndreas Boehler
365a495d34cSAndreas Boehler      foreach($settings as $key => $value)
366a495d34cSAndreas Boehler      {
36751f4febbSAndreas Boehler          $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)";
36851f4febbSAndreas Boehler          $res = $this->sqlite->query($query, $userid, $key, $value);
369a495d34cSAndreas Boehler          if($res === false)
370a495d34cSAndreas Boehler              return false;
371a495d34cSAndreas Boehler      }
372a495d34cSAndreas Boehler      $this->sqlite->query("COMMIT TRANSACTION");
373185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
374a495d34cSAndreas Boehler      return true;
375a495d34cSAndreas Boehler  }
376a495d34cSAndreas Boehler
377cb71a62aSAndreas Boehler  /**
378cb71a62aSAndreas Boehler   * Retrieve the settings array for a given user id.
379cb71a62aSAndreas Boehler   * Some sane defaults are returned, currently:
380cb71a62aSAndreas Boehler   *
381cb71a62aSAndreas Boehler   *    timezone    => local
382cb71a62aSAndreas Boehler   *    weeknumbers => 0
383cb71a62aSAndreas Boehler   *    workweek    => 0
384cb71a62aSAndreas Boehler   *
385cb71a62aSAndreas Boehler   * @param string $userid (optional) The user id to retrieve
386cb71a62aSAndreas Boehler   *
387cb71a62aSAndreas Boehler   * @return array The settings array
388cb71a62aSAndreas Boehler   */
389a495d34cSAndreas Boehler  public function getPersonalSettings($userid = null)
390a495d34cSAndreas Boehler  {
391bd883736SAndreas Boehler      // Some sane default settings
392bd883736SAndreas Boehler      $settings = array(
393fb813b30SAndreas Boehler        'timezone' => $this->getConf('timezone'),
394fb813b30SAndreas Boehler        'weeknumbers' => $this->getConf('weeknumbers'),
395fb813b30SAndreas Boehler        'workweek' => $this->getConf('workweek'),
3961d5bdcd0SAndreas Boehler        'monday' => $this->getConf('monday'),
3971d5bdcd0SAndreas Boehler        'timeformat' => $this->getConf('timeformat')
398bd883736SAndreas Boehler      );
39934a47953SAndreas Boehler      if(is_null($userid))
40034a47953SAndreas Boehler      {
40134a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
40234a47953SAndreas Boehler          {
40334a47953SAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
40434a47953SAndreas Boehler          }
40534a47953SAndreas Boehler          else
40634a47953SAndreas Boehler          {
40734a47953SAndreas Boehler            return $settings;
40834a47953SAndreas Boehler          }
40934a47953SAndreas Boehler      }
41034a47953SAndreas Boehler
41134a47953SAndreas Boehler      if(isset($this->cachedValues['settings'][$userid]))
41234a47953SAndreas Boehler        return $this->cachedValues['settings'][$userid];
41351f4febbSAndreas Boehler      $query = "SELECT key, value FROM calendarsettings WHERE userid = ?";
41451f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $userid);
415a495d34cSAndreas Boehler      $arr = $this->sqlite->res2arr($res);
416a495d34cSAndreas Boehler      foreach($arr as $row)
417a495d34cSAndreas Boehler      {
418a495d34cSAndreas Boehler          $settings[$row['key']] = $row['value'];
419a495d34cSAndreas Boehler      }
420185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
421a495d34cSAndreas Boehler      return $settings;
422a495d34cSAndreas Boehler  }
423a495d34cSAndreas Boehler
424cb71a62aSAndreas Boehler  /**
425cb71a62aSAndreas Boehler   * Retrieve the calendar ID based on a page ID from the SQLite table
426cb71a62aSAndreas Boehler   * 'pagetocalendarmapping'.
427cb71a62aSAndreas Boehler   *
428cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to retrieve the corresponding calendar
429cb71a62aSAndreas Boehler   *
430cb71a62aSAndreas Boehler   * @return mixed the ID on success, otherwise false
431cb71a62aSAndreas Boehler   */
432a1a3b679SAndreas Boehler  public function getCalendarIdForPage($id = null)
433a1a3b679SAndreas Boehler  {
434a1a3b679SAndreas Boehler      if(is_null($id))
435a1a3b679SAndreas Boehler      {
436a1a3b679SAndreas Boehler          global $ID;
437a1a3b679SAndreas Boehler          $id = $ID;
438a1a3b679SAndreas Boehler      }
439a1a3b679SAndreas Boehler
440185e2535SAndreas Boehler      if(isset($this->cachedValues['calid'][$id]))
441185e2535SAndreas Boehler        return $this->cachedValues['calid'][$id];
442185e2535SAndreas Boehler
44351f4febbSAndreas Boehler      $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?";
44451f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $id);
445a1a3b679SAndreas Boehler      $row = $this->sqlite->res2row($res);
446a1a3b679SAndreas Boehler      if(isset($row['calid']))
447185e2535SAndreas Boehler      {
448185e2535SAndreas Boehler        $calid = $row['calid'];
449185e2535SAndreas Boehler        $this->cachedValues['calid'] = $calid;
450185e2535SAndreas Boehler        return $calid;
451185e2535SAndreas Boehler      }
452a1a3b679SAndreas Boehler      return false;
453a1a3b679SAndreas Boehler  }
454a1a3b679SAndreas Boehler
455cb71a62aSAndreas Boehler  /**
456cb71a62aSAndreas Boehler   * Retrieve the complete calendar id to page mapping.
457cb71a62aSAndreas Boehler   * This is necessary to be able to retrieve a list of
458cb71a62aSAndreas Boehler   * calendars for a given user and check the access rights.
459cb71a62aSAndreas Boehler   *
460cb71a62aSAndreas Boehler   * @return array The mapping array
461cb71a62aSAndreas Boehler   */
462a1a3b679SAndreas Boehler  public function getCalendarIdToPageMapping()
463a1a3b679SAndreas Boehler  {
464a1a3b679SAndreas Boehler      $query = "SELECT calid, page FROM pagetocalendarmapping";
465a1a3b679SAndreas Boehler      $res = $this->sqlite->query($query);
466a1a3b679SAndreas Boehler      $arr = $this->sqlite->res2arr($res);
467a1a3b679SAndreas Boehler      return $arr;
468a1a3b679SAndreas Boehler  }
469a1a3b679SAndreas Boehler
470cb71a62aSAndreas Boehler  /**
471cb71a62aSAndreas Boehler   * Retrieve all calendar IDs a given user has access to.
472cb71a62aSAndreas Boehler   * The user is specified by the principalUri, so the
473cb71a62aSAndreas Boehler   * user name is actually split from the URI component.
474cb71a62aSAndreas Boehler   *
475cb71a62aSAndreas Boehler   * Access rights are checked against DokuWiki's ACL
476cb71a62aSAndreas Boehler   * and applied accordingly.
477cb71a62aSAndreas Boehler   *
478cb71a62aSAndreas Boehler   * @param string $principalUri The principal URI to work on
479cb71a62aSAndreas Boehler   *
480cb71a62aSAndreas Boehler   * @return array An associative array of calendar IDs
481cb71a62aSAndreas Boehler   */
482a1a3b679SAndreas Boehler  public function getCalendarIdsForUser($principalUri)
483a1a3b679SAndreas Boehler  {
48434a47953SAndreas Boehler      global $auth;
485a1a3b679SAndreas Boehler      $user = explode('/', $principalUri);
486a1a3b679SAndreas Boehler      $user = end($user);
487a1a3b679SAndreas Boehler      $mapping = $this->getCalendarIdToPageMapping();
488a1a3b679SAndreas Boehler      $calids = array();
48934a47953SAndreas Boehler      $ud = $auth->getUserData($user);
49034a47953SAndreas Boehler      $groups = $ud['grps'];
491a1a3b679SAndreas Boehler      foreach($mapping as $row)
492a1a3b679SAndreas Boehler      {
493a1a3b679SAndreas Boehler          $id = $row['calid'];
494a1a3b679SAndreas Boehler          $page = $row['page'];
49534a47953SAndreas Boehler          $acl = auth_aclcheck($page, $user, $groups);
496a1a3b679SAndreas Boehler          if($acl >= AUTH_READ)
497a1a3b679SAndreas Boehler          {
498a1a3b679SAndreas Boehler              $write = $acl > AUTH_READ;
499a1a3b679SAndreas Boehler              $calids[$id] = array('readonly' => !$write);
500a1a3b679SAndreas Boehler          }
501a1a3b679SAndreas Boehler      }
502a1a3b679SAndreas Boehler      return $calids;
503a1a3b679SAndreas Boehler  }
504a1a3b679SAndreas Boehler
505cb71a62aSAndreas Boehler  /**
506cb71a62aSAndreas Boehler   * Create a new calendar for a given page ID and set name and description
507cb71a62aSAndreas Boehler   * accordingly. Also update the pagetocalendarmapping table on success.
508cb71a62aSAndreas Boehler   *
509cb71a62aSAndreas Boehler   * @param string $name The calendar's name
510cb71a62aSAndreas Boehler   * @param string $description The calendar's description
511cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to work on
512cb71a62aSAndreas Boehler   * @param string $userid (optional) The user ID that created the calendar
513cb71a62aSAndreas Boehler   *
514cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
515cb71a62aSAndreas Boehler   */
516a1a3b679SAndreas Boehler  public function createCalendarForPage($name, $description, $id = null, $userid = null)
517a1a3b679SAndreas Boehler  {
518a1a3b679SAndreas Boehler      if(is_null($id))
519a1a3b679SAndreas Boehler      {
520a1a3b679SAndreas Boehler          global $ID;
521a1a3b679SAndreas Boehler          $id = $ID;
522a1a3b679SAndreas Boehler      }
523a1a3b679SAndreas Boehler      if(is_null($userid))
52434a47953SAndreas Boehler      {
52534a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
52634a47953SAndreas Boehler        {
527a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
52834a47953SAndreas Boehler        }
52934a47953SAndreas Boehler        else
53034a47953SAndreas Boehler        {
53134a47953SAndreas Boehler          $userid = uniqid('davcal-');
53234a47953SAndreas Boehler        }
53334a47953SAndreas Boehler      }
534a1a3b679SAndreas Boehler      $values = array('principals/'.$userid,
535a1a3b679SAndreas Boehler                      $name,
536a1a3b679SAndreas Boehler                      str_replace(array('/', ' ', ':'), '_', $id),
537a1a3b679SAndreas Boehler                      $description,
538a1a3b679SAndreas Boehler                      'VEVENT,VTODO',
53955a741c0SAndreas Boehler                      0,
54055a741c0SAndreas Boehler                      1);
54151f4febbSAndreas Boehler      $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ".
54251f4febbSAndreas Boehler               "VALUES (?, ?, ?, ?, ?, ?, ?)";
54351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]);
54455a741c0SAndreas Boehler      if($res === false)
54555a741c0SAndreas Boehler        return false;
546cb71a62aSAndreas Boehler
547cb71a62aSAndreas Boehler      // Get the new calendar ID
54851f4febbSAndreas Boehler      $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ".
54951f4febbSAndreas Boehler               "uri = ? AND description = ?";
55051f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $values[0], $values[1], $values[2], $values[3]);
551a1a3b679SAndreas Boehler      $row = $this->sqlite->res2row($res);
552cb71a62aSAndreas Boehler
553cb71a62aSAndreas Boehler      // Update the pagetocalendarmapping table with the new calendar ID
554a1a3b679SAndreas Boehler      if(isset($row['id']))
555a1a3b679SAndreas Boehler      {
55651f4febbSAndreas Boehler          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)";
55751f4febbSAndreas Boehler          $res = $this->sqlite->query($query, $id, $row['id']);
55855a741c0SAndreas Boehler          return ($res !== false);
559a1a3b679SAndreas Boehler      }
560a1a3b679SAndreas Boehler
561a1a3b679SAndreas Boehler      return false;
562a1a3b679SAndreas Boehler  }
563a1a3b679SAndreas Boehler
564cb71a62aSAndreas Boehler  /**
565*d5703f5aSAndreas Boehler   * Add a new calendar entry to the given calendar. Calendar data is
566*d5703f5aSAndreas Boehler   * specified as ICS file, thus it needs to be parsed first.
567*d5703f5aSAndreas Boehler   *
568*d5703f5aSAndreas Boehler   * This is mainly needed for the sync support.
569*d5703f5aSAndreas Boehler   *
570*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
571*d5703f5aSAndreas Boehler   * @param string $uri The new object URI
572*d5703f5aSAndreas Boehler   * @param string $ics The ICS file
573*d5703f5aSAndreas Boehler   *
574*d5703f5aSAndreas Boehler   * @return mixed The etag.
575*d5703f5aSAndreas Boehler   */
576*d5703f5aSAndreas Boehler  public function addCalendarEntryToCalendarByICS($calid, $uri, $ics)
577*d5703f5aSAndreas Boehler  {
578*d5703f5aSAndreas Boehler    $extraData = $this->getDenormalizedData($ics);
579*d5703f5aSAndreas Boehler
580*d5703f5aSAndreas Boehler    $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)";
581*d5703f5aSAndreas Boehler    $res = $this->sqlite->query($query,
582*d5703f5aSAndreas Boehler            $calid,
583*d5703f5aSAndreas Boehler            $uri,
584*d5703f5aSAndreas Boehler            $ics,
585*d5703f5aSAndreas Boehler            time(),
586*d5703f5aSAndreas Boehler            $extraData['etag'],
587*d5703f5aSAndreas Boehler            $extraData['size'],
588*d5703f5aSAndreas Boehler            $extraData['componentType'],
589*d5703f5aSAndreas Boehler            $extraData['firstOccurence'],
590*d5703f5aSAndreas Boehler            $extraData['lastOccurence'],
591*d5703f5aSAndreas Boehler            $extraData['uid']);
592*d5703f5aSAndreas Boehler            // If successfully, update the sync token database
593*d5703f5aSAndreas Boehler    if($res !== false)
594*d5703f5aSAndreas Boehler    {
595*d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, $uri, 'added');
596*d5703f5aSAndreas Boehler    }
597*d5703f5aSAndreas Boehler    return $extraData['etag'];
598*d5703f5aSAndreas Boehler  }
599*d5703f5aSAndreas Boehler
600*d5703f5aSAndreas Boehler  /**
601*d5703f5aSAndreas Boehler   * Edit a calendar entry by providing a new ICS file. This is mainly
602*d5703f5aSAndreas Boehler   * needed for the sync support.
603*d5703f5aSAndreas Boehler   *
604*d5703f5aSAndreas Boehler   * @param int $calid The calendar's IS
605*d5703f5aSAndreas Boehler   * @param string $uri The object's URI to modify
606*d5703f5aSAndreas Boehler   * @param string $ics The new object's ICS file
607*d5703f5aSAndreas Boehler   */
608*d5703f5aSAndreas Boehler  public function editCalendarEntryToCalendarByICS($calid, $uri, $ics)
609*d5703f5aSAndreas Boehler  {
610*d5703f5aSAndreas Boehler      $extraData = $this->getDenormalizedData($ics);
611*d5703f5aSAndreas Boehler
612*d5703f5aSAndreas Boehler      $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?";
613*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query,
614*d5703f5aSAndreas Boehler        $ics,
615*d5703f5aSAndreas Boehler        time(),
616*d5703f5aSAndreas Boehler        $extraData['etag'],
617*d5703f5aSAndreas Boehler        $extraData['size'],
618*d5703f5aSAndreas Boehler        $extraData['componentType'],
619*d5703f5aSAndreas Boehler        $extraData['firstOccurence'],
620*d5703f5aSAndreas Boehler        $extraData['lastOccurence'],
621*d5703f5aSAndreas Boehler        $extraData['uid'],
622*d5703f5aSAndreas Boehler        $calid,
623*d5703f5aSAndreas Boehler        $uri
624*d5703f5aSAndreas Boehler      );
625*d5703f5aSAndreas Boehler      if($res !== false)
626*d5703f5aSAndreas Boehler      {
627*d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'modified');
628*d5703f5aSAndreas Boehler      }
629*d5703f5aSAndreas Boehler      return $extraData['etag'];
630*d5703f5aSAndreas Boehler  }
631*d5703f5aSAndreas Boehler
632*d5703f5aSAndreas Boehler  /**
633cb71a62aSAndreas Boehler   * Add a new iCal entry for a given page, i.e. a given calendar.
634cb71a62aSAndreas Boehler   *
635cb71a62aSAndreas Boehler   * The parameter array needs to contain
636cb71a62aSAndreas Boehler   *   detectedtz       => The timezone as detected by the browser
63782a48dfbSAndreas Boehler   *   currenttz        => The timezone in use by the calendar
638cb71a62aSAndreas Boehler   *   eventfrom        => The event's start date
639cb71a62aSAndreas Boehler   *   eventfromtime    => The event's start time
640cb71a62aSAndreas Boehler   *   eventto          => The event's end date
641cb71a62aSAndreas Boehler   *   eventtotime      => The event's end time
642cb71a62aSAndreas Boehler   *   eventname        => The event's name
643cb71a62aSAndreas Boehler   *   eventdescription => The event's description
644cb71a62aSAndreas Boehler   *
645cb71a62aSAndreas Boehler   * @param string $id The page ID to work on
646cb71a62aSAndreas Boehler   * @param string $user The user who created the calendar
647cb71a62aSAndreas Boehler   * @param string $params A parameter array with values to create
648cb71a62aSAndreas Boehler   *
649cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
650cb71a62aSAndreas Boehler   */
651a1a3b679SAndreas Boehler  public function addCalendarEntryToCalendarForPage($id, $user, $params)
652a1a3b679SAndreas Boehler  {
65382a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
65482a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
65582a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
656a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
657bd883736SAndreas Boehler      else
658bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
659cb71a62aSAndreas Boehler
660cb71a62aSAndreas Boehler      // Retrieve dates from settings
661b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
662b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
663b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
664b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
665cb71a62aSAndreas Boehler
666cb71a62aSAndreas Boehler      // Load SabreDAV
6679bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
668a1a3b679SAndreas Boehler      $vcalendar = new \Sabre\VObject\Component\VCalendar();
669cb71a62aSAndreas Boehler
670cb71a62aSAndreas Boehler      // Add VCalendar, UID and Event Name
671a1a3b679SAndreas Boehler      $event = $vcalendar->add('VEVENT');
672b269830cSAndreas Boehler      $uuid = \Sabre\VObject\UUIDUtil::getUUID();
673b269830cSAndreas Boehler      $event->add('UID', $uuid);
674a1a3b679SAndreas Boehler      $event->summary = $params['eventname'];
675cb71a62aSAndreas Boehler
676cb71a62aSAndreas Boehler      // Add a description if requested
6770eebc909SAndreas Boehler      $description = $params['eventdescription'];
6780eebc909SAndreas Boehler      if($description !== '')
6790eebc909SAndreas Boehler        $event->add('DESCRIPTION', $description);
680cb71a62aSAndreas Boehler
6814ecb526cSAndreas Boehler      // Add attachments
6824ecb526cSAndreas Boehler      $attachments = $params['attachments'];
68382a48dfbSAndreas Boehler      if(!is_null($attachments))
6844ecb526cSAndreas Boehler        foreach($attachments as $attachment)
6854ecb526cSAndreas Boehler          $event->add('ATTACH', $attachment);
6864ecb526cSAndreas Boehler
687cb71a62aSAndreas Boehler      // Create a timestamp for last modified, created and dtstamp values in UTC
688b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
689b269830cSAndreas Boehler      $event->add('DTSTAMP', $dtStamp);
690b269830cSAndreas Boehler      $event->add('CREATED', $dtStamp);
691b269830cSAndreas Boehler      $event->add('LAST-MODIFIED', $dtStamp);
692cb71a62aSAndreas Boehler
693cb71a62aSAndreas Boehler      // Adjust the start date, based on the given timezone information
694b269830cSAndreas Boehler      $dtStart = new \DateTime();
695a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
696b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
697cb71a62aSAndreas Boehler
698cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
699b269830cSAndreas Boehler      if($params['allday'] != '1')
700b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
701cb71a62aSAndreas Boehler
702cb71a62aSAndreas Boehler      // Adjust the end date, based on the given timezone information
703b269830cSAndreas Boehler      $dtEnd = new \DateTime();
704a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
705b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
706cb71a62aSAndreas Boehler
707cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
708b269830cSAndreas Boehler      if($params['allday'] != '1')
709b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
710cb71a62aSAndreas Boehler
711b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
712b269830cSAndreas Boehler      if($params['allday'] == '1')
713b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
714cb71a62aSAndreas Boehler
715cb71a62aSAndreas Boehler      // Really add Start and End events
716b269830cSAndreas Boehler      $dtStartEv = $event->add('DTSTART', $dtStart);
717b269830cSAndreas Boehler      $dtEndEv = $event->add('DTEND', $dtEnd);
718cb71a62aSAndreas Boehler
719cb71a62aSAndreas Boehler      // Adjust the DATE format for allday events
720b269830cSAndreas Boehler      if($params['allday'] == '1')
721b269830cSAndreas Boehler      {
722b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
723b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
724b269830cSAndreas Boehler      }
725cb71a62aSAndreas Boehler
726cb71a62aSAndreas Boehler      // Actually add the values to the database
727a1a3b679SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
728a1a3b679SAndreas Boehler      $uri = uniqid('dokuwiki-').'.ics';
729a1a3b679SAndreas Boehler      $now = new DateTime();
730a1a3b679SAndreas Boehler      $eventStr = $vcalendar->serialize();
731a1a3b679SAndreas Boehler
73251f4febbSAndreas Boehler      $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
73351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT',
73451f4febbSAndreas Boehler                                  $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(),
73551f4febbSAndreas Boehler                                  strlen($eventStr), md5($eventStr), $uuid);
736cb71a62aSAndreas Boehler
737cb71a62aSAndreas Boehler      // If successfully, update the sync token database
73855a741c0SAndreas Boehler      if($res !== false)
73955a741c0SAndreas Boehler      {
74055a741c0SAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'added');
741a1a3b679SAndreas Boehler          return true;
742a1a3b679SAndreas Boehler      }
74355a741c0SAndreas Boehler      return false;
74455a741c0SAndreas Boehler  }
745a1a3b679SAndreas Boehler
746cb71a62aSAndreas Boehler  /**
747cb71a62aSAndreas Boehler   * Retrieve the calendar settings of a given calendar id
748cb71a62aSAndreas Boehler   *
749cb71a62aSAndreas Boehler   * @param string $calid The calendar ID
750cb71a62aSAndreas Boehler   *
751cb71a62aSAndreas Boehler   * @return array The calendar settings array
752cb71a62aSAndreas Boehler   */
753b269830cSAndreas Boehler  public function getCalendarSettings($calid)
754b269830cSAndreas Boehler  {
755*d5703f5aSAndreas Boehler      $query = "SELECT id, principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken FROM calendars WHERE id= ? ";
75651f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $calid);
757b269830cSAndreas Boehler      $row = $this->sqlite->res2row($res);
758b269830cSAndreas Boehler      return $row;
759b269830cSAndreas Boehler  }
760b269830cSAndreas Boehler
761cb71a62aSAndreas Boehler  /**
762cb71a62aSAndreas Boehler   * Retrieve all events that are within a given date range,
763cb71a62aSAndreas Boehler   * based on the timezone setting.
764cb71a62aSAndreas Boehler   *
765cb71a62aSAndreas Boehler   * There is also support for retrieving recurring events,
766cb71a62aSAndreas Boehler   * using Sabre's VObject Iterator. Recurring events are represented
767cb71a62aSAndreas Boehler   * as individual calendar entries with the same UID.
768cb71a62aSAndreas Boehler   *
769cb71a62aSAndreas Boehler   * @param string $id The page ID to work with
770cb71a62aSAndreas Boehler   * @param string $user The user ID to work with
771cb71a62aSAndreas Boehler   * @param string $startDate The start date as a string
772cb71a62aSAndreas Boehler   * @param string $endDate The end date as a string
7734a2bf5eeSAndreas Boehler   * @param string $color (optional) The calendar's color
774cb71a62aSAndreas Boehler   *
775cb71a62aSAndreas Boehler   * @return array An array containing the calendar entries.
776cb71a62aSAndreas Boehler   */
7774a2bf5eeSAndreas Boehler  public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null)
778a1a3b679SAndreas Boehler  {
77982a48dfbSAndreas Boehler      if($timezone !== '' && $timezone !== 'local')
78082a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($timezone);
781bd883736SAndreas Boehler      else
782bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
783a1a3b679SAndreas Boehler      $data = array();
784cb71a62aSAndreas Boehler
785cb71a62aSAndreas Boehler      // Load SabreDAV
7869bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
787a1a3b679SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
7884a2bf5eeSAndreas Boehler      if(is_null($color))
789185e2535SAndreas Boehler        $color = $this->getCalendarColorForCalendar($calid);
790a469597cSAndreas Boehler      $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?";
791a469597cSAndreas Boehler      $startTs = null;
792a469597cSAndreas Boehler      $endTs = null;
793a469597cSAndreas Boehler      if($startDate !== null)
794a469597cSAndreas Boehler      {
795a1a3b679SAndreas Boehler        $startTs = new \DateTime($startDate);
796a469597cSAndreas Boehler        $query .= " AND lastoccurence > ".$this->sqlite->quote_string($startTs->getTimestamp());
797a469597cSAndreas Boehler      }
798a469597cSAndreas Boehler      if($endDate !== null)
799a469597cSAndreas Boehler      {
800a1a3b679SAndreas Boehler        $endTs = new \DateTime($endDate);
801a469597cSAndreas Boehler        $query .= " AND firstoccurence < ".$this->sqlite->quote_string($endTs->getTimestamp());
802a469597cSAndreas Boehler      }
803cb71a62aSAndreas Boehler
804cb71a62aSAndreas Boehler      // Retrieve matching calendar objects
805a469597cSAndreas Boehler      $res = $this->sqlite->query($query, $calid);
806a1a3b679SAndreas Boehler      $arr = $this->sqlite->res2arr($res);
807cb71a62aSAndreas Boehler
808cb71a62aSAndreas Boehler      // Parse individual calendar entries
809a1a3b679SAndreas Boehler      foreach($arr as $row)
810a1a3b679SAndreas Boehler      {
811a1a3b679SAndreas Boehler          if(isset($row['calendardata']))
812a1a3b679SAndreas Boehler          {
813b269830cSAndreas Boehler              $entry = array();
814a1a3b679SAndreas Boehler              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
815ebc4eb57SAndreas Boehler              $recurrence = $vcal->VEVENT->RRULE;
816cb71a62aSAndreas Boehler              // If it is a recurring event, pass it through Sabre's EventIterator
817ebc4eb57SAndreas Boehler              if($recurrence != null)
818ebc4eb57SAndreas Boehler              {
819ebc4eb57SAndreas Boehler                  $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT));
820ebc4eb57SAndreas Boehler                  $rEvents->rewind();
821e9b7d302SAndreas Boehler                  while($rEvents->valid())
822ebc4eb57SAndreas Boehler                  {
823ebc4eb57SAndreas Boehler                      $event = $rEvents->getEventObject();
824cb71a62aSAndreas Boehler                      // If we are after the given time range, exit
825a469597cSAndreas Boehler                      if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()))
826e9b7d302SAndreas Boehler                          break;
827cb71a62aSAndreas Boehler
828cb71a62aSAndreas Boehler                      // If we are before the given time range, continue
829a469597cSAndreas Boehler                      if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()))
830ebc4eb57SAndreas Boehler                      {
831ebc4eb57SAndreas Boehler                          $rEvents->next();
832ebc4eb57SAndreas Boehler                          continue;
833ebc4eb57SAndreas Boehler                      }
834cb71a62aSAndreas Boehler
835cb71a62aSAndreas Boehler                      // If we are within the given time range, parse the event
836185e2535SAndreas Boehler                      $data[] = $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true);
837ebc4eb57SAndreas Boehler                      $rEvents->next();
838ebc4eb57SAndreas Boehler                  }
839ebc4eb57SAndreas Boehler              }
840ebc4eb57SAndreas Boehler              else
841185e2535SAndreas Boehler                $data[] = $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color);
842ebc4eb57SAndreas Boehler          }
843ebc4eb57SAndreas Boehler      }
844ebc4eb57SAndreas Boehler      return $data;
845ebc4eb57SAndreas Boehler  }
846ebc4eb57SAndreas Boehler
847cb71a62aSAndreas Boehler  /**
848cb71a62aSAndreas Boehler   * Helper function that parses the iCal data of a VEVENT to a calendar entry.
849cb71a62aSAndreas Boehler   *
850cb71a62aSAndreas Boehler   * @param \Sabre\VObject\VEvent $event The event to parse
851cb71a62aSAndreas Boehler   * @param \DateTimeZone $timezone The timezone object
852cb71a62aSAndreas Boehler   * @param string $uid The entry's UID
8533c86dda8SAndreas Boehler   * @param boolean $recurring (optional) Set to true to define a recurring event
854cb71a62aSAndreas Boehler   *
855cb71a62aSAndreas Boehler   * @return array The parse calendar entry
856cb71a62aSAndreas Boehler   */
857185e2535SAndreas Boehler  private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false)
858ebc4eb57SAndreas Boehler  {
859ebc4eb57SAndreas Boehler      $entry = array();
860ebc4eb57SAndreas Boehler      $start = $event->DTSTART;
861cb71a62aSAndreas Boehler      // Parse only if the start date/time is present
862b269830cSAndreas Boehler      if($start !== null)
863b269830cSAndreas Boehler      {
864b269830cSAndreas Boehler        $dtStart = $start->getDateTime();
865b269830cSAndreas Boehler        $dtStart->setTimezone($timezone);
866bf0ad2b4SAndreas Boehler
867bf0ad2b4SAndreas Boehler        // moment.js doesn't like times be given even if
868bf0ad2b4SAndreas Boehler        // allDay is set to true
869bf0ad2b4SAndreas Boehler        // This should fix T23
870b269830cSAndreas Boehler        if($start['VALUE'] == 'DATE')
871bf0ad2b4SAndreas Boehler        {
872b269830cSAndreas Boehler          $entry['allDay'] = true;
873bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format("Y-m-d");
874bf0ad2b4SAndreas Boehler        }
875b269830cSAndreas Boehler        else
876bf0ad2b4SAndreas Boehler        {
877b269830cSAndreas Boehler          $entry['allDay'] = false;
878bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format(\DateTime::ATOM);
879bf0ad2b4SAndreas Boehler        }
880b269830cSAndreas Boehler      }
881ebc4eb57SAndreas Boehler      $end = $event->DTEND;
882bf0ad2b4SAndreas Boehler      // Parse only if the end date/time is present
883b269830cSAndreas Boehler      if($end !== null)
884b269830cSAndreas Boehler      {
885b269830cSAndreas Boehler        $dtEnd = $end->getDateTime();
886b269830cSAndreas Boehler        $dtEnd->setTimezone($timezone);
887bf0ad2b4SAndreas Boehler        if($end['VALUE'] == 'DATE')
888bf0ad2b4SAndreas Boehler          $entry['end'] = $dtEnd->format("Y-m-d");
889bf0ad2b4SAndreas Boehler        else
890b269830cSAndreas Boehler          $entry['end'] = $dtEnd->format(\DateTime::ATOM);
891b269830cSAndreas Boehler      }
892ebc4eb57SAndreas Boehler      $description = $event->DESCRIPTION;
8930eebc909SAndreas Boehler      if($description !== null)
8940eebc909SAndreas Boehler        $entry['description'] = (string)$description;
8950eebc909SAndreas Boehler      else
8960eebc909SAndreas Boehler        $entry['description'] = '';
8974ecb526cSAndreas Boehler      $attachments = $event->ATTACH;
8984ecb526cSAndreas Boehler      if($attachments !== null)
8994ecb526cSAndreas Boehler      {
9004ecb526cSAndreas Boehler        $entry['attachments'] = array();
9014ecb526cSAndreas Boehler        foreach($attachments as $attachment)
9024ecb526cSAndreas Boehler          $entry['attachments'][] = (string)$attachment;
9034ecb526cSAndreas Boehler      }
904ebc4eb57SAndreas Boehler      $entry['title'] = (string)$event->summary;
905ebc4eb57SAndreas Boehler      $entry['id'] = $uid;
906185e2535SAndreas Boehler      $entry['page'] = $page;
907185e2535SAndreas Boehler      $entry['color'] = $color;
9083c86dda8SAndreas Boehler      $entry['recurring'] = $recurring;
909185e2535SAndreas Boehler
910ebc4eb57SAndreas Boehler      return $entry;
911a1a3b679SAndreas Boehler  }
912a1a3b679SAndreas Boehler
913cb71a62aSAndreas Boehler  /**
914cb71a62aSAndreas Boehler   * Retrieve an event by its UID
915cb71a62aSAndreas Boehler   *
916cb71a62aSAndreas Boehler   * @param string $uid The event's UID
917cb71a62aSAndreas Boehler   *
918cb71a62aSAndreas Boehler   * @return mixed The table row with the given event
919cb71a62aSAndreas Boehler   */
920a1a3b679SAndreas Boehler  public function getEventWithUid($uid)
921a1a3b679SAndreas Boehler  {
92251f4febbSAndreas Boehler      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid = ?";
92351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $uid);
924a1a3b679SAndreas Boehler      $row = $this->sqlite->res2row($res);
925a1a3b679SAndreas Boehler      return $row;
926a1a3b679SAndreas Boehler  }
927a1a3b679SAndreas Boehler
928cb71a62aSAndreas Boehler  /**
929*d5703f5aSAndreas Boehler   * Retrieve information of a calendar's object, not including the actual
930*d5703f5aSAndreas Boehler   * calendar data! This is mainly neede for the sync support.
931*d5703f5aSAndreas Boehler   *
932*d5703f5aSAndreas Boehler   * @param int $calid The calendar ID
933*d5703f5aSAndreas Boehler   *
934*d5703f5aSAndreas Boehler   * @return mixed The result
935*d5703f5aSAndreas Boehler   */
936*d5703f5aSAndreas Boehler  public function getCalendarObjects($calid)
937*d5703f5aSAndreas Boehler  {
938*d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM calendarobjects WHERE calendarid = ?";
939*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid);
940*d5703f5aSAndreas Boehler      $arr = $this->sqlite->res2arr($res);
941*d5703f5aSAndreas Boehler      return $arr;
942*d5703f5aSAndreas Boehler  }
943*d5703f5aSAndreas Boehler
944*d5703f5aSAndreas Boehler  /**
945*d5703f5aSAndreas Boehler   * Retrieve a single calendar object by calendar ID and URI
946*d5703f5aSAndreas Boehler   *
947*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
948*d5703f5aSAndreas Boehler   * @param string $uri The object's URI
949*d5703f5aSAndreas Boehler   *
950*d5703f5aSAndreas Boehler   * @return mixed The result
951*d5703f5aSAndreas Boehler   */
952*d5703f5aSAndreas Boehler  public function getCalendarObjectByUri($calid, $uri)
953*d5703f5aSAndreas Boehler  {
954*d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri = ?";
955*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $uri);
956*d5703f5aSAndreas Boehler      $row = $this->sqlite->res2row($res);
957*d5703f5aSAndreas Boehler      return $row;
958*d5703f5aSAndreas Boehler  }
959*d5703f5aSAndreas Boehler
960*d5703f5aSAndreas Boehler  /**
961*d5703f5aSAndreas Boehler   * Retrieve several calendar objects by specifying an array of URIs.
962*d5703f5aSAndreas Boehler   * This is mainly neede for sync.
963*d5703f5aSAndreas Boehler   *
964*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
965*d5703f5aSAndreas Boehler   * @param array $uris An array of URIs
966*d5703f5aSAndreas Boehler   *
967*d5703f5aSAndreas Boehler   * @return mixed The result
968*d5703f5aSAndreas Boehler   */
969*d5703f5aSAndreas Boehler  public function getMultipleCalendarObjectsByUri($calid, $uris)
970*d5703f5aSAndreas Boehler  {
971*d5703f5aSAndreas Boehler        $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri IN (";
972*d5703f5aSAndreas Boehler        // Inserting a whole bunch of question marks
973*d5703f5aSAndreas Boehler        $query .= implode(',', array_fill(0, count($uris), '?'));
974*d5703f5aSAndreas Boehler        $query .= ')';
975*d5703f5aSAndreas Boehler        $vals = array_merge(array($calid), $uris);
976*d5703f5aSAndreas Boehler
977*d5703f5aSAndreas Boehler        $res = $this->sqlite->query($query, $vals);
978*d5703f5aSAndreas Boehler        $arr = $this->sqlite->res2arr($res);
979*d5703f5aSAndreas Boehler        return $arr;
980*d5703f5aSAndreas Boehler  }
981*d5703f5aSAndreas Boehler
982*d5703f5aSAndreas Boehler  /**
983cb71a62aSAndreas Boehler   * Retrieve all calendar events for a given calendar ID
984cb71a62aSAndreas Boehler   *
985cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
986cb71a62aSAndreas Boehler   *
987cb71a62aSAndreas Boehler   * @return array An array containing all calendar data
988cb71a62aSAndreas Boehler   */
989f69bb449SAndreas Boehler  public function getAllCalendarEvents($calid)
990f69bb449SAndreas Boehler  {
9917e0b8590SAndreas Boehler      $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?";
99251f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $calid);
993f69bb449SAndreas Boehler      $arr = $this->sqlite->res2arr($res);
994f69bb449SAndreas Boehler      return $arr;
995f69bb449SAndreas Boehler  }
996f69bb449SAndreas Boehler
997cb71a62aSAndreas Boehler  /**
998cb71a62aSAndreas Boehler   * Edit a calendar entry for a page, given by its parameters.
999cb71a62aSAndreas Boehler   * The params array has the same format as @see addCalendarEntryForPage
1000cb71a62aSAndreas Boehler   *
1001cb71a62aSAndreas Boehler   * @param string $id The page's ID to work on
1002cb71a62aSAndreas Boehler   * @param string $user The user's ID to work on
1003cb71a62aSAndreas Boehler   * @param array $params The parameter array for the edited calendar event
1004cb71a62aSAndreas Boehler   *
1005cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1006cb71a62aSAndreas Boehler   */
1007a1a3b679SAndreas Boehler  public function editCalendarEntryForPage($id, $user, $params)
1008a1a3b679SAndreas Boehler  {
100982a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
101082a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
101182a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
1012a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
1013bd883736SAndreas Boehler      else
1014bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
1015cb71a62aSAndreas Boehler
1016cb71a62aSAndreas Boehler      // Parse dates
1017b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
1018b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
1019b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
1020b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
1021cb71a62aSAndreas Boehler
1022cb71a62aSAndreas Boehler      // Retrieve the existing event based on the UID
102355a741c0SAndreas Boehler      $uid = $params['uid'];
102455a741c0SAndreas Boehler      $event = $this->getEventWithUid($uid);
1025cb71a62aSAndreas Boehler
1026cb71a62aSAndreas Boehler      // Load SabreDAV
10279bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1028a1a3b679SAndreas Boehler      if(!isset($event['calendardata']))
1029a1a3b679SAndreas Boehler        return false;
103055a741c0SAndreas Boehler      $uri = $event['uri'];
103155a741c0SAndreas Boehler      $calid = $event['calendarid'];
1032cb71a62aSAndreas Boehler
1033cb71a62aSAndreas Boehler      // Parse the existing event
1034a1a3b679SAndreas Boehler      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
1035b269830cSAndreas Boehler      $vevent = $vcal->VEVENT;
1036cb71a62aSAndreas Boehler
1037cb71a62aSAndreas Boehler      // Set the new event values
1038b269830cSAndreas Boehler      $vevent->summary = $params['eventname'];
1039b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
10400eebc909SAndreas Boehler      $description = $params['eventdescription'];
1041cb71a62aSAndreas Boehler
1042cb71a62aSAndreas Boehler      // Remove existing timestamps to overwrite them
10430eebc909SAndreas Boehler      $vevent->remove('DESCRIPTION');
1044b269830cSAndreas Boehler      $vevent->remove('DTSTAMP');
1045b269830cSAndreas Boehler      $vevent->remove('LAST-MODIFIED');
10464ecb526cSAndreas Boehler      $vevent->remove('ATTACH');
1047cb71a62aSAndreas Boehler
1048cb71a62aSAndreas Boehler      // Add new time stamps and description
1049b269830cSAndreas Boehler      $vevent->add('DTSTAMP', $dtStamp);
1050b269830cSAndreas Boehler      $vevent->add('LAST-MODIFIED', $dtStamp);
10510eebc909SAndreas Boehler      if($description !== '')
10520eebc909SAndreas Boehler        $vevent->add('DESCRIPTION', $description);
1053cb71a62aSAndreas Boehler
10544ecb526cSAndreas Boehler      // Add attachments
10554ecb526cSAndreas Boehler      $attachments = $params['attachments'];
105682a48dfbSAndreas Boehler      if(!is_null($attachments))
10574ecb526cSAndreas Boehler        foreach($attachments as $attachment)
10584ecb526cSAndreas Boehler          $vevent->add('ATTACH', $attachment);
10594ecb526cSAndreas Boehler
1060cb71a62aSAndreas Boehler      // Setup DTSTART
1061b269830cSAndreas Boehler      $dtStart = new \DateTime();
1062a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
1063b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
1064b269830cSAndreas Boehler      if($params['allday'] != '1')
1065b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
1066cb71a62aSAndreas Boehler
10674ecb526cSAndreas Boehler      // Setup DTEND
1068b269830cSAndreas Boehler      $dtEnd = new \DateTime();
1069a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
1070b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
1071b269830cSAndreas Boehler      if($params['allday'] != '1')
1072b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
1073cb71a62aSAndreas Boehler
1074b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
1075b269830cSAndreas Boehler      if($params['allday'] == '1')
1076b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
1077b269830cSAndreas Boehler      $vevent->remove('DTSTART');
1078b269830cSAndreas Boehler      $vevent->remove('DTEND');
1079b269830cSAndreas Boehler      $dtStartEv = $vevent->add('DTSTART', $dtStart);
1080b269830cSAndreas Boehler      $dtEndEv = $vevent->add('DTEND', $dtEnd);
1081cb71a62aSAndreas Boehler
1082cb71a62aSAndreas Boehler      // Remove the time for allday events
1083b269830cSAndreas Boehler      if($params['allday'] == '1')
1084b269830cSAndreas Boehler      {
1085b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
1086b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
1087b269830cSAndreas Boehler      }
1088a1a3b679SAndreas Boehler      $now = new DateTime();
1089a1a3b679SAndreas Boehler      $eventStr = $vcal->serialize();
1090cb71a62aSAndreas Boehler      // Actually write to the database
109151f4febbSAndreas Boehler      $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ".
109251f4febbSAndreas Boehler               "firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?";
109351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(),
109451f4febbSAndreas Boehler                                  $dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid);
109555a741c0SAndreas Boehler      if($res !== false)
109655a741c0SAndreas Boehler      {
109755a741c0SAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'modified');
1098a1a3b679SAndreas Boehler          return true;
1099a1a3b679SAndreas Boehler      }
110055a741c0SAndreas Boehler      return false;
110155a741c0SAndreas Boehler  }
1102a1a3b679SAndreas Boehler
1103cb71a62aSAndreas Boehler  /**
1104*d5703f5aSAndreas Boehler   * Delete an event from a calendar by calendar ID and URI
1105*d5703f5aSAndreas Boehler   *
1106*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1107*d5703f5aSAndreas Boehler   * @param string $uri The object's URI
1108*d5703f5aSAndreas Boehler   *
1109*d5703f5aSAndreas Boehler   * @return true
1110*d5703f5aSAndreas Boehler   */
1111*d5703f5aSAndreas Boehler  public function deleteCalendarEntryForCalendarByUri($calid, $uri)
1112*d5703f5aSAndreas Boehler  {
1113*d5703f5aSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?";
1114*d5703f5aSAndreas Boehler      $res = $this->sqlite->query($query, $calid, $uri);
1115*d5703f5aSAndreas Boehler      if($res !== false)
1116*d5703f5aSAndreas Boehler      {
1117*d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
1118*d5703f5aSAndreas Boehler      }
1119*d5703f5aSAndreas Boehler      return true;
1120*d5703f5aSAndreas Boehler  }
1121*d5703f5aSAndreas Boehler
1122*d5703f5aSAndreas Boehler  /**
1123cb71a62aSAndreas Boehler   * Delete a calendar entry for a given page. Actually, the event is removed
1124cb71a62aSAndreas Boehler   * based on the entry's UID, so that page ID is no used.
1125cb71a62aSAndreas Boehler   *
1126cb71a62aSAndreas Boehler   * @param string $id The page's ID (unused)
1127cb71a62aSAndreas Boehler   * @param array $params The parameter array to work with
1128cb71a62aSAndreas Boehler   *
1129cb71a62aSAndreas Boehler   * @return boolean True
1130cb71a62aSAndreas Boehler   */
1131a1a3b679SAndreas Boehler  public function deleteCalendarEntryForPage($id, $params)
1132a1a3b679SAndreas Boehler  {
1133a1a3b679SAndreas Boehler      $uid = $params['uid'];
113455a741c0SAndreas Boehler      $event = $this->getEventWithUid($uid);
11352c14b82bSAndreas Boehler      $calid = $event['calendarid'];
113655a741c0SAndreas Boehler      $uri = $event['uri'];
113751f4febbSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE uid = ?";
113851f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $uid);
113955a741c0SAndreas Boehler      if($res !== false)
114055a741c0SAndreas Boehler      {
114155a741c0SAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
114255a741c0SAndreas Boehler      }
1143a1a3b679SAndreas Boehler      return true;
1144a1a3b679SAndreas Boehler  }
1145a1a3b679SAndreas Boehler
1146cb71a62aSAndreas Boehler  /**
1147cb71a62aSAndreas Boehler   * Retrieve the current sync token for a calendar
1148cb71a62aSAndreas Boehler   *
1149cb71a62aSAndreas Boehler   * @param string $calid The calendar id
1150cb71a62aSAndreas Boehler   *
1151cb71a62aSAndreas Boehler   * @return mixed The synctoken or false
1152cb71a62aSAndreas Boehler   */
115355a741c0SAndreas Boehler  public function getSyncTokenForCalendar($calid)
115455a741c0SAndreas Boehler  {
1155b269830cSAndreas Boehler      $row = $this->getCalendarSettings($calid);
115655a741c0SAndreas Boehler      if(isset($row['synctoken']))
115755a741c0SAndreas Boehler          return $row['synctoken'];
115855a741c0SAndreas Boehler      return false;
115955a741c0SAndreas Boehler  }
116055a741c0SAndreas Boehler
1161cb71a62aSAndreas Boehler  /**
1162cb71a62aSAndreas Boehler   * Helper function to convert the operation name to
1163cb71a62aSAndreas Boehler   * an operation code as stored in the database
1164cb71a62aSAndreas Boehler   *
1165cb71a62aSAndreas Boehler   * @param string $operationName The operation name
1166cb71a62aSAndreas Boehler   *
1167cb71a62aSAndreas Boehler   * @return mixed The operation code or false
1168cb71a62aSAndreas Boehler   */
116955a741c0SAndreas Boehler  public function operationNameToOperation($operationName)
117055a741c0SAndreas Boehler  {
117155a741c0SAndreas Boehler      switch($operationName)
117255a741c0SAndreas Boehler      {
117355a741c0SAndreas Boehler          case 'added':
117455a741c0SAndreas Boehler              return 1;
117555a741c0SAndreas Boehler          break;
117655a741c0SAndreas Boehler          case 'modified':
117755a741c0SAndreas Boehler              return 2;
117855a741c0SAndreas Boehler          break;
117955a741c0SAndreas Boehler          case 'deleted':
118055a741c0SAndreas Boehler              return 3;
118155a741c0SAndreas Boehler          break;
118255a741c0SAndreas Boehler      }
118355a741c0SAndreas Boehler      return false;
118455a741c0SAndreas Boehler  }
118555a741c0SAndreas Boehler
1186cb71a62aSAndreas Boehler  /**
1187cb71a62aSAndreas Boehler   * Update the sync token log based on the calendar id and the
1188cb71a62aSAndreas Boehler   * operation that was performed.
1189cb71a62aSAndreas Boehler   *
1190cb71a62aSAndreas Boehler   * @param string $calid The calendar ID that was modified
1191cb71a62aSAndreas Boehler   * @param string $uri The calendar URI that was modified
1192cb71a62aSAndreas Boehler   * @param string $operation The operation that was performed
1193cb71a62aSAndreas Boehler   *
1194cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1195cb71a62aSAndreas Boehler   */
119655a741c0SAndreas Boehler  private function updateSyncTokenLog($calid, $uri, $operation)
119755a741c0SAndreas Boehler  {
119855a741c0SAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
119955a741c0SAndreas Boehler      $operationCode = $this->operationNameToOperation($operation);
120055a741c0SAndreas Boehler      if(($operationCode === false) || ($currentToken === false))
120155a741c0SAndreas Boehler          return false;
120255a741c0SAndreas Boehler      $values = array($uri,
120355a741c0SAndreas Boehler                      $currentToken,
120455a741c0SAndreas Boehler                      $calid,
120555a741c0SAndreas Boehler                      $operationCode
120655a741c0SAndreas Boehler      );
120751f4febbSAndreas Boehler      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)";
120851f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $uri, $currentToken, $calid, $operationCode);
120955a741c0SAndreas Boehler      if($res === false)
121055a741c0SAndreas Boehler        return false;
121155a741c0SAndreas Boehler      $currentToken++;
121251f4febbSAndreas Boehler      $query = "UPDATE calendars SET synctoken = ? WHERE id = ?";
121351f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $currentToken, $calid);
121455a741c0SAndreas Boehler      return ($res !== false);
121555a741c0SAndreas Boehler  }
121655a741c0SAndreas Boehler
1217cb71a62aSAndreas Boehler  /**
1218cb71a62aSAndreas Boehler   * Return the sync URL for a given Page, i.e. a calendar
1219cb71a62aSAndreas Boehler   *
1220cb71a62aSAndreas Boehler   * @param string $id The page's ID
1221cb71a62aSAndreas Boehler   * @param string $user (optional) The user's ID
1222cb71a62aSAndreas Boehler   *
1223cb71a62aSAndreas Boehler   * @return mixed The sync url or false
1224cb71a62aSAndreas Boehler   */
1225b269830cSAndreas Boehler  public function getSyncUrlForPage($id, $user = null)
1226b269830cSAndreas Boehler  {
122734a47953SAndreas Boehler      if(is_null($userid))
122834a47953SAndreas Boehler      {
122934a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
123034a47953SAndreas Boehler        {
123134a47953SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
123234a47953SAndreas Boehler        }
123334a47953SAndreas Boehler        else
123434a47953SAndreas Boehler        {
123534a47953SAndreas Boehler          return false;
123634a47953SAndreas Boehler        }
123734a47953SAndreas Boehler      }
1238b269830cSAndreas Boehler
1239b269830cSAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1240b269830cSAndreas Boehler      if($calid === false)
1241b269830cSAndreas Boehler        return false;
1242b269830cSAndreas Boehler
1243b269830cSAndreas Boehler      $calsettings = $this->getCalendarSettings($calid);
1244b269830cSAndreas Boehler      if(!isset($calsettings['uri']))
1245b269830cSAndreas Boehler        return false;
1246b269830cSAndreas Boehler
1247b269830cSAndreas Boehler      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
1248b269830cSAndreas Boehler      return $syncurl;
1249b269830cSAndreas Boehler  }
1250b269830cSAndreas Boehler
1251cb71a62aSAndreas Boehler  /**
1252cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given page
1253cb71a62aSAndreas Boehler   *
1254cb71a62aSAndreas Boehler   * @param string $id the page ID
1255cb71a62aSAndreas Boehler   *
1256cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1257cb71a62aSAndreas Boehler   */
1258f69bb449SAndreas Boehler  public function getPrivateURLForPage($id)
1259f69bb449SAndreas Boehler  {
1260f69bb449SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1261f69bb449SAndreas Boehler      if($calid === false)
1262f69bb449SAndreas Boehler        return false;
1263f69bb449SAndreas Boehler
1264f69bb449SAndreas Boehler      return $this->getPrivateURLForCalendar($calid);
1265f69bb449SAndreas Boehler  }
1266f69bb449SAndreas Boehler
1267cb71a62aSAndreas Boehler  /**
1268cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given calendar ID
1269cb71a62aSAndreas Boehler   *
1270cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
1271cb71a62aSAndreas Boehler   *
1272cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1273cb71a62aSAndreas Boehler   */
1274f69bb449SAndreas Boehler  public function getPrivateURLForCalendar($calid)
1275f69bb449SAndreas Boehler  {
1276185e2535SAndreas Boehler      if(isset($this->cachedValues['privateurl'][$calid]))
1277185e2535SAndreas Boehler        return $this->cachedValues['privateurl'][$calid];
127851f4febbSAndreas Boehler      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
127951f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $calid);
1280f69bb449SAndreas Boehler      $row = $this->sqlite->res2row($res);
1281f69bb449SAndreas Boehler      if(!isset($row['url']))
1282f69bb449SAndreas Boehler      {
1283f69bb449SAndreas Boehler          $url = uniqid("dokuwiki-").".ics";
128451f4febbSAndreas Boehler          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
128551f4febbSAndreas Boehler          $res = $this->sqlite->query($query, $url, $calid);
1286f69bb449SAndreas Boehler          if($res === false)
1287f69bb449SAndreas Boehler            return false;
1288f69bb449SAndreas Boehler      }
1289f69bb449SAndreas Boehler      else
1290f69bb449SAndreas Boehler      {
1291f69bb449SAndreas Boehler          $url = $row['url'];
1292f69bb449SAndreas Boehler      }
1293185e2535SAndreas Boehler
1294185e2535SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
1295185e2535SAndreas Boehler      $this->cachedValues['privateurl'][$calid] = $url;
1296185e2535SAndreas Boehler      return $url;
1297f69bb449SAndreas Boehler  }
1298f69bb449SAndreas Boehler
1299cb71a62aSAndreas Boehler  /**
1300cb71a62aSAndreas Boehler   * Retrieve the calendar ID for a given private calendar URL
1301cb71a62aSAndreas Boehler   *
1302cb71a62aSAndreas Boehler   * @param string $url The private URL
1303cb71a62aSAndreas Boehler   *
1304cb71a62aSAndreas Boehler   * @return mixed The calendar ID or false
1305cb71a62aSAndreas Boehler   */
1306f69bb449SAndreas Boehler  public function getCalendarForPrivateURL($url)
1307f69bb449SAndreas Boehler  {
130851f4febbSAndreas Boehler      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
130951f4febbSAndreas Boehler      $res = $this->sqlite->query($query, $url);
1310f69bb449SAndreas Boehler      $row = $this->sqlite->res2row($res);
1311f69bb449SAndreas Boehler      if(!isset($row['calid']))
1312f69bb449SAndreas Boehler        return false;
1313f69bb449SAndreas Boehler      return $row['calid'];
1314f69bb449SAndreas Boehler  }
1315f69bb449SAndreas Boehler
1316cb71a62aSAndreas Boehler  /**
1317cb71a62aSAndreas Boehler   * Return a given calendar as ICS feed, i.e. all events in one ICS file.
1318cb71a62aSAndreas Boehler   *
13197e0b8590SAndreas Boehler   * @param string $calid The calendar ID to retrieve
1320cb71a62aSAndreas Boehler   *
1321cb71a62aSAndreas Boehler   * @return mixed The calendar events as string or false
1322cb71a62aSAndreas Boehler   */
1323f69bb449SAndreas Boehler  public function getCalendarAsICSFeed($calid)
1324f69bb449SAndreas Boehler  {
1325f69bb449SAndreas Boehler      $calSettings = $this->getCalendarSettings($calid);
1326f69bb449SAndreas Boehler      if($calSettings === false)
1327f69bb449SAndreas Boehler        return false;
1328f69bb449SAndreas Boehler      $events = $this->getAllCalendarEvents($calid);
1329f69bb449SAndreas Boehler      if($events === false)
1330f69bb449SAndreas Boehler        return false;
1331f69bb449SAndreas Boehler
13327e0b8590SAndreas Boehler      // Load SabreDAV
13337e0b8590SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
13347e0b8590SAndreas Boehler      $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
13357e0b8590SAndreas Boehler      $out .= $calSettings['displayname']."\r\n";
1336f69bb449SAndreas Boehler      foreach($events as $event)
1337f69bb449SAndreas Boehler      {
13387e0b8590SAndreas Boehler          $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
13397e0b8590SAndreas Boehler          $evt = $vcal->VEVENT;
13407e0b8590SAndreas Boehler          $out .= $evt->serialize();
1341f69bb449SAndreas Boehler      }
13427e0b8590SAndreas Boehler      $out .= "END:VCALENDAR\r\n";
1343f69bb449SAndreas Boehler      return $out;
1344f69bb449SAndreas Boehler  }
1345f69bb449SAndreas Boehler
13467c7c6b0bSAndreas Boehler  /**
13477c7c6b0bSAndreas Boehler   * Retrieve a configuration option for the plugin
13487c7c6b0bSAndreas Boehler   *
13497c7c6b0bSAndreas Boehler   * @param string $key The key to query
135021d04f73SAndreas Boehler   * @return mixed The option set, null if not found
13517c7c6b0bSAndreas Boehler   */
13527c7c6b0bSAndreas Boehler  public function getConfig($key)
13537c7c6b0bSAndreas Boehler  {
13547c7c6b0bSAndreas Boehler      return $this->getConf($key);
13557c7c6b0bSAndreas Boehler  }
13567c7c6b0bSAndreas Boehler
1357*d5703f5aSAndreas Boehler  /**
1358*d5703f5aSAndreas Boehler   * Parses some information from calendar objects, used for optimized
1359*d5703f5aSAndreas Boehler   * calendar-queries. Taken nearly unmodified from Sabre's PDO backend
1360*d5703f5aSAndreas Boehler   *
1361*d5703f5aSAndreas Boehler   * Returns an array with the following keys:
1362*d5703f5aSAndreas Boehler   *   * etag - An md5 checksum of the object without the quotes.
1363*d5703f5aSAndreas Boehler   *   * size - Size of the object in bytes
1364*d5703f5aSAndreas Boehler   *   * componentType - VEVENT, VTODO or VJOURNAL
1365*d5703f5aSAndreas Boehler   *   * firstOccurence
1366*d5703f5aSAndreas Boehler   *   * lastOccurence
1367*d5703f5aSAndreas Boehler   *   * uid - value of the UID property
1368*d5703f5aSAndreas Boehler   *
1369*d5703f5aSAndreas Boehler   * @param string $calendarData
1370*d5703f5aSAndreas Boehler   * @return array
1371*d5703f5aSAndreas Boehler   */
1372*d5703f5aSAndreas Boehler  protected function getDenormalizedData($calendarData)
1373*d5703f5aSAndreas Boehler  {
1374*d5703f5aSAndreas Boehler    require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1375*d5703f5aSAndreas Boehler
1376*d5703f5aSAndreas Boehler    $vObject = \Sabre\VObject\Reader::read($calendarData);
1377*d5703f5aSAndreas Boehler    $componentType = null;
1378*d5703f5aSAndreas Boehler    $component = null;
1379*d5703f5aSAndreas Boehler    $firstOccurence = null;
1380*d5703f5aSAndreas Boehler    $lastOccurence = null;
1381*d5703f5aSAndreas Boehler    $uid = null;
1382*d5703f5aSAndreas Boehler    foreach ($vObject->getComponents() as $component)
1383*d5703f5aSAndreas Boehler    {
1384*d5703f5aSAndreas Boehler        if ($component->name !== 'VTIMEZONE')
1385*d5703f5aSAndreas Boehler        {
1386*d5703f5aSAndreas Boehler            $componentType = $component->name;
1387*d5703f5aSAndreas Boehler            $uid = (string)$component->UID;
1388*d5703f5aSAndreas Boehler            break;
1389*d5703f5aSAndreas Boehler        }
1390*d5703f5aSAndreas Boehler    }
1391*d5703f5aSAndreas Boehler    if (!$componentType)
1392*d5703f5aSAndreas Boehler    {
1393*d5703f5aSAndreas Boehler        return false;
1394*d5703f5aSAndreas Boehler    }
1395*d5703f5aSAndreas Boehler    if ($componentType === 'VEVENT')
1396*d5703f5aSAndreas Boehler    {
1397*d5703f5aSAndreas Boehler        $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
1398*d5703f5aSAndreas Boehler        // Finding the last occurence is a bit harder
1399*d5703f5aSAndreas Boehler        if (!isset($component->RRULE))
1400*d5703f5aSAndreas Boehler        {
1401*d5703f5aSAndreas Boehler            if (isset($component->DTEND))
1402*d5703f5aSAndreas Boehler            {
1403*d5703f5aSAndreas Boehler                $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
1404*d5703f5aSAndreas Boehler            }
1405*d5703f5aSAndreas Boehler            elseif (isset($component->DURATION))
1406*d5703f5aSAndreas Boehler            {
1407*d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1408*d5703f5aSAndreas Boehler                $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->getValue()));
1409*d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1410*d5703f5aSAndreas Boehler            }
1411*d5703f5aSAndreas Boehler            elseif (!$component->DTSTART->hasTime())
1412*d5703f5aSAndreas Boehler            {
1413*d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1414*d5703f5aSAndreas Boehler                $endDate->modify('+1 day');
1415*d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1416*d5703f5aSAndreas Boehler            }
1417*d5703f5aSAndreas Boehler            else
1418*d5703f5aSAndreas Boehler            {
1419*d5703f5aSAndreas Boehler                $lastOccurence = $firstOccurence;
1420*d5703f5aSAndreas Boehler            }
1421*d5703f5aSAndreas Boehler        }
1422*d5703f5aSAndreas Boehler        else
1423*d5703f5aSAndreas Boehler        {
1424*d5703f5aSAndreas Boehler            $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID);
1425*d5703f5aSAndreas Boehler            $maxDate = new \DateTime('2038-01-01');
1426*d5703f5aSAndreas Boehler            if ($it->isInfinite())
1427*d5703f5aSAndreas Boehler            {
1428*d5703f5aSAndreas Boehler                $lastOccurence = $maxDate->getTimeStamp();
1429*d5703f5aSAndreas Boehler            }
1430*d5703f5aSAndreas Boehler            else
1431*d5703f5aSAndreas Boehler            {
1432*d5703f5aSAndreas Boehler                $end = $it->getDtEnd();
1433*d5703f5aSAndreas Boehler                while ($it->valid() && $end < $maxDate)
1434*d5703f5aSAndreas Boehler                {
1435*d5703f5aSAndreas Boehler                    $end = $it->getDtEnd();
1436*d5703f5aSAndreas Boehler                    $it->next();
1437*d5703f5aSAndreas Boehler                }
1438*d5703f5aSAndreas Boehler                $lastOccurence = $end->getTimeStamp();
1439*d5703f5aSAndreas Boehler            }
1440*d5703f5aSAndreas Boehler        }
1441*d5703f5aSAndreas Boehler    }
1442*d5703f5aSAndreas Boehler
1443*d5703f5aSAndreas Boehler    return array(
1444*d5703f5aSAndreas Boehler        'etag'           => md5($calendarData),
1445*d5703f5aSAndreas Boehler        'size'           => strlen($calendarData),
1446*d5703f5aSAndreas Boehler        'componentType'  => $componentType,
1447*d5703f5aSAndreas Boehler        'firstOccurence' => $firstOccurence,
1448*d5703f5aSAndreas Boehler        'lastOccurence'  => $lastOccurence,
1449*d5703f5aSAndreas Boehler        'uid'            => $uid,
1450*d5703f5aSAndreas Boehler    );
1451*d5703f5aSAndreas Boehler
1452*d5703f5aSAndreas Boehler  }
1453*d5703f5aSAndreas Boehler
1454*d5703f5aSAndreas Boehler  /**
1455*d5703f5aSAndreas Boehler   * Query a calendar by ID and taking several filters into account.
1456*d5703f5aSAndreas Boehler   * This is heavily based on Sabre's PDO backend.
1457*d5703f5aSAndreas Boehler   *
1458*d5703f5aSAndreas Boehler   * @param int $calendarId The calendar's ID
1459*d5703f5aSAndreas Boehler   * @param array $filters The filter array to apply
1460*d5703f5aSAndreas Boehler   *
1461*d5703f5aSAndreas Boehler   * @return mixed The result
1462*d5703f5aSAndreas Boehler   */
1463*d5703f5aSAndreas Boehler  public function calendarQuery($calendarId, $filters)
1464*d5703f5aSAndreas Boehler  {
1465*d5703f5aSAndreas Boehler    $componentType = null;
1466*d5703f5aSAndreas Boehler    $requirePostFilter = true;
1467*d5703f5aSAndreas Boehler    $timeRange = null;
1468*d5703f5aSAndreas Boehler
1469*d5703f5aSAndreas Boehler    // if no filters were specified, we don't need to filter after a query
1470*d5703f5aSAndreas Boehler    if (!$filters['prop-filters'] && !$filters['comp-filters'])
1471*d5703f5aSAndreas Boehler    {
1472*d5703f5aSAndreas Boehler        $requirePostFilter = false;
1473*d5703f5aSAndreas Boehler    }
1474*d5703f5aSAndreas Boehler
1475*d5703f5aSAndreas Boehler    // Figuring out if there's a component filter
1476*d5703f5aSAndreas Boehler    if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined'])
1477*d5703f5aSAndreas Boehler    {
1478*d5703f5aSAndreas Boehler        $componentType = $filters['comp-filters'][0]['name'];
1479*d5703f5aSAndreas Boehler
1480*d5703f5aSAndreas Boehler        // Checking if we need post-filters
1481*d5703f5aSAndreas Boehler        if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters'])
1482*d5703f5aSAndreas Boehler        {
1483*d5703f5aSAndreas Boehler            $requirePostFilter = false;
1484*d5703f5aSAndreas Boehler        }
1485*d5703f5aSAndreas Boehler        // There was a time-range filter
1486*d5703f5aSAndreas Boehler        if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range']))
1487*d5703f5aSAndreas Boehler        {
1488*d5703f5aSAndreas Boehler            $timeRange = $filters['comp-filters'][0]['time-range'];
1489*d5703f5aSAndreas Boehler
1490*d5703f5aSAndreas Boehler            // If start time OR the end time is not specified, we can do a
1491*d5703f5aSAndreas Boehler            // 100% accurate mysql query.
1492*d5703f5aSAndreas Boehler            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end']))
1493*d5703f5aSAndreas Boehler            {
1494*d5703f5aSAndreas Boehler                $requirePostFilter = false;
1495*d5703f5aSAndreas Boehler            }
1496*d5703f5aSAndreas Boehler        }
1497*d5703f5aSAndreas Boehler
1498*d5703f5aSAndreas Boehler    }
1499*d5703f5aSAndreas Boehler
1500*d5703f5aSAndreas Boehler    if ($requirePostFilter)
1501*d5703f5aSAndreas Boehler    {
1502*d5703f5aSAndreas Boehler        $query = "SELECT uri, calendardata FROM calendarobjects WHERE calendarid = ?";
1503*d5703f5aSAndreas Boehler    }
1504*d5703f5aSAndreas Boehler    else
1505*d5703f5aSAndreas Boehler    {
1506*d5703f5aSAndreas Boehler        $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
1507*d5703f5aSAndreas Boehler    }
1508*d5703f5aSAndreas Boehler
1509*d5703f5aSAndreas Boehler    $values = array(
1510*d5703f5aSAndreas Boehler        $calendarId
1511*d5703f5aSAndreas Boehler    );
1512*d5703f5aSAndreas Boehler
1513*d5703f5aSAndreas Boehler    if ($componentType)
1514*d5703f5aSAndreas Boehler    {
1515*d5703f5aSAndreas Boehler        $query .= " AND componenttype = ?";
1516*d5703f5aSAndreas Boehler        $values[] = $componentType;
1517*d5703f5aSAndreas Boehler    }
1518*d5703f5aSAndreas Boehler
1519*d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['start'])
1520*d5703f5aSAndreas Boehler    {
1521*d5703f5aSAndreas Boehler        $query .= " AND lastoccurence > ?";
1522*d5703f5aSAndreas Boehler        $values[] = $timeRange['start']->getTimeStamp();
1523*d5703f5aSAndreas Boehler    }
1524*d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['end'])
1525*d5703f5aSAndreas Boehler    {
1526*d5703f5aSAndreas Boehler        $query .= " AND firstoccurence < ?";
1527*d5703f5aSAndreas Boehler        $values[] = $timeRange['end']->getTimeStamp();
1528*d5703f5aSAndreas Boehler    }
1529*d5703f5aSAndreas Boehler
1530*d5703f5aSAndreas Boehler    $res = $this->sqlite->query($query, $values);
1531*d5703f5aSAndreas Boehler    $arr = $this->sqlite->res2arr($res);
1532*d5703f5aSAndreas Boehler
1533*d5703f5aSAndreas Boehler    $result = array();
1534*d5703f5aSAndreas Boehler    foreach($arr as $row)
1535*d5703f5aSAndreas Boehler    {
1536*d5703f5aSAndreas Boehler        if ($requirePostFilter)
1537*d5703f5aSAndreas Boehler        {
1538*d5703f5aSAndreas Boehler            if (!$this->validateFilterForObject($row, $filters))
1539*d5703f5aSAndreas Boehler            {
1540*d5703f5aSAndreas Boehler                continue;
1541*d5703f5aSAndreas Boehler            }
1542*d5703f5aSAndreas Boehler        }
1543*d5703f5aSAndreas Boehler        $result[] = $row['uri'];
1544*d5703f5aSAndreas Boehler
1545*d5703f5aSAndreas Boehler    }
1546*d5703f5aSAndreas Boehler
1547*d5703f5aSAndreas Boehler    return $result;
1548*d5703f5aSAndreas Boehler  }
1549*d5703f5aSAndreas Boehler
1550*d5703f5aSAndreas Boehler  /**
1551*d5703f5aSAndreas Boehler   * This method validates if a filter (as passed to calendarQuery) matches
1552*d5703f5aSAndreas Boehler   * the given object. Taken from Sabre's PDO backend
1553*d5703f5aSAndreas Boehler   *
1554*d5703f5aSAndreas Boehler   * @param array $object
1555*d5703f5aSAndreas Boehler   * @param array $filters
1556*d5703f5aSAndreas Boehler   * @return bool
1557*d5703f5aSAndreas Boehler   */
1558*d5703f5aSAndreas Boehler  protected function validateFilterForObject($object, $filters)
1559*d5703f5aSAndreas Boehler  {
1560*d5703f5aSAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1561*d5703f5aSAndreas Boehler      // Unfortunately, setting the 'calendardata' here is optional. If
1562*d5703f5aSAndreas Boehler      // it was excluded, we actually need another call to get this as
1563*d5703f5aSAndreas Boehler      // well.
1564*d5703f5aSAndreas Boehler      if (!isset($object['calendardata']))
1565*d5703f5aSAndreas Boehler      {
1566*d5703f5aSAndreas Boehler          $object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']);
1567*d5703f5aSAndreas Boehler      }
1568*d5703f5aSAndreas Boehler
1569*d5703f5aSAndreas Boehler      $vObject = \Sabre\VObject\Reader::read($object['calendardata']);
1570*d5703f5aSAndreas Boehler      $validator = new \Sabre\CalDAV\CalendarQueryValidator();
1571*d5703f5aSAndreas Boehler
1572*d5703f5aSAndreas Boehler      return $validator->validate($vObject, $filters);
1573*d5703f5aSAndreas Boehler
1574*d5703f5aSAndreas Boehler  }
1575*d5703f5aSAndreas Boehler
1576*d5703f5aSAndreas Boehler  /**
1577*d5703f5aSAndreas Boehler   * Retrieve changes for a given calendar based on the given syncToken.
1578*d5703f5aSAndreas Boehler   *
1579*d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1580*d5703f5aSAndreas Boehler   * @param int $syncToken The supplied sync token
1581*d5703f5aSAndreas Boehler   * @param int $syncLevel The sync level
1582*d5703f5aSAndreas Boehler   * @param int $limit The limit of changes
1583*d5703f5aSAndreas Boehler   *
1584*d5703f5aSAndreas Boehler   * @return array The result
1585*d5703f5aSAndreas Boehler   */
1586*d5703f5aSAndreas Boehler  public function getChangesForCalendar($calid, $syncToken, $syncLevel, $limit = null)
1587*d5703f5aSAndreas Boehler  {
1588*d5703f5aSAndreas Boehler      // Current synctoken
1589*d5703f5aSAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
1590*d5703f5aSAndreas Boehler
1591*d5703f5aSAndreas Boehler      if ($currentToken === false) return null;
1592*d5703f5aSAndreas Boehler
1593*d5703f5aSAndreas Boehler      $result = array(
1594*d5703f5aSAndreas Boehler          'syncToken' => $currentToken,
1595*d5703f5aSAndreas Boehler          'added'     => array(),
1596*d5703f5aSAndreas Boehler          'modified'  => array(),
1597*d5703f5aSAndreas Boehler          'deleted'   => array(),
1598*d5703f5aSAndreas Boehler      );
1599*d5703f5aSAndreas Boehler
1600*d5703f5aSAndreas Boehler      if ($syncToken)
1601*d5703f5aSAndreas Boehler      {
1602*d5703f5aSAndreas Boehler
1603*d5703f5aSAndreas Boehler          $query = "SELECT uri, operation FROM calendarchanges WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
1604*d5703f5aSAndreas Boehler          if ($limit > 0) $query .= " LIMIT " . (int)$limit;
1605*d5703f5aSAndreas Boehler
1606*d5703f5aSAndreas Boehler          // Fetching all changes
1607*d5703f5aSAndreas Boehler          $res = $this->sqlite->query($query, $syncToken, $currentToken, $calid);
1608*d5703f5aSAndreas Boehler          if($res === false)
1609*d5703f5aSAndreas Boehler              return null;
1610*d5703f5aSAndreas Boehler
1611*d5703f5aSAndreas Boehler          $arr = $this->sqlite->res2arr($res);
1612*d5703f5aSAndreas Boehler          $changes = array();
1613*d5703f5aSAndreas Boehler
1614*d5703f5aSAndreas Boehler          // This loop ensures that any duplicates are overwritten, only the
1615*d5703f5aSAndreas Boehler          // last change on a node is relevant.
1616*d5703f5aSAndreas Boehler          foreach($arr as $row)
1617*d5703f5aSAndreas Boehler          {
1618*d5703f5aSAndreas Boehler              $changes[$row['uri']] = $row['operation'];
1619*d5703f5aSAndreas Boehler          }
1620*d5703f5aSAndreas Boehler
1621*d5703f5aSAndreas Boehler          foreach ($changes as $uri => $operation)
1622*d5703f5aSAndreas Boehler          {
1623*d5703f5aSAndreas Boehler              switch ($operation)
1624*d5703f5aSAndreas Boehler              {
1625*d5703f5aSAndreas Boehler                  case 1 :
1626*d5703f5aSAndreas Boehler                      $result['added'][] = $uri;
1627*d5703f5aSAndreas Boehler                      break;
1628*d5703f5aSAndreas Boehler                  case 2 :
1629*d5703f5aSAndreas Boehler                      $result['modified'][] = $uri;
1630*d5703f5aSAndreas Boehler                      break;
1631*d5703f5aSAndreas Boehler                  case 3 :
1632*d5703f5aSAndreas Boehler                      $result['deleted'][] = $uri;
1633*d5703f5aSAndreas Boehler                      break;
1634*d5703f5aSAndreas Boehler              }
1635*d5703f5aSAndreas Boehler
1636*d5703f5aSAndreas Boehler          }
1637*d5703f5aSAndreas Boehler      }
1638*d5703f5aSAndreas Boehler      else
1639*d5703f5aSAndreas Boehler      {
1640*d5703f5aSAndreas Boehler          // No synctoken supplied, this is the initial sync.
1641*d5703f5aSAndreas Boehler          $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
1642*d5703f5aSAndreas Boehler          $res = $this->sqlite->query($query);
1643*d5703f5aSAndreas Boehler          $arr = $this->sqlite->res2arr($res);
1644*d5703f5aSAndreas Boehler
1645*d5703f5aSAndreas Boehler          $result['added'] = $arr;
1646*d5703f5aSAndreas Boehler      }
1647*d5703f5aSAndreas Boehler      return $result;
1648*d5703f5aSAndreas Boehler  }
1649*d5703f5aSAndreas Boehler
1650a1a3b679SAndreas Boehler}
1651