xref: /plugin/davcal/helper.php (revision 7e2d24364d66cf56aeb8ce10a8e9fe37ec58692c)
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    */
19306344e5SGerrit Uitslag  public function __construct() {
20c42afaebSscottleechua    \dokuwiki\Logger::debug('DAVCAL', 'Helper initialized', __FILE__, __LINE__);
215f2c3e2dSAndreas Boehler  }
225f2c3e2dSAndreas Boehler
235f2c3e2dSAndreas Boehler  /** Establish and initialize the database if not already done
245f2c3e2dSAndreas Boehler   * @return sqlite interface or false
255f2c3e2dSAndreas Boehler   */
265f2c3e2dSAndreas Boehler  private function getDB()
275f2c3e2dSAndreas Boehler  {
285f2c3e2dSAndreas Boehler      if($this->sqlite === null)
295f2c3e2dSAndreas Boehler      {
30*7e2d2436Sscottleechua        $this->sqlite = new \dokuwiki\plugin\sqlite\SQLiteDB('davcal', DOKU_PLUGIN.'davcal/db/');
31a1a3b679SAndreas Boehler        if(!$this->sqlite)
32a1a3b679SAndreas Boehler        {
33c42afaebSscottleechua            \dokuwiki\Logger::error('DAVCAL', 'This plugin requires the sqlite plugin. Please install it.', __FILE__, __LINE__);
345f2c3e2dSAndreas Boehler            msg('This plugin requires the sqlite plugin. Please install it.', -1);
355f2c3e2dSAndreas Boehler            return false;
36a1a3b679SAndreas Boehler        }
37a1a3b679SAndreas Boehler      }
385f2c3e2dSAndreas Boehler      return $this->sqlite;
395f2c3e2dSAndreas Boehler  }
40a1a3b679SAndreas Boehler
41cb71a62aSAndreas Boehler  /**
42185e2535SAndreas Boehler   * Retrieve meta data for a given page
43185e2535SAndreas Boehler   *
44185e2535SAndreas Boehler   * @param string $id optional The page ID
45185e2535SAndreas Boehler   * @return array The metadata
46185e2535SAndreas Boehler   */
47185e2535SAndreas Boehler  private function getMeta($id = null) {
48185e2535SAndreas Boehler    global $ID;
49185e2535SAndreas Boehler    global $INFO;
50185e2535SAndreas Boehler
51185e2535SAndreas Boehler    if ($id === null) $id = $ID;
52185e2535SAndreas Boehler
53185e2535SAndreas Boehler    if($ID === $id && $INFO['meta']) {
54185e2535SAndreas Boehler        $meta = $INFO['meta'];
55185e2535SAndreas Boehler    } else {
56185e2535SAndreas Boehler        $meta = p_get_metadata($id);
57185e2535SAndreas Boehler    }
58185e2535SAndreas Boehler
59185e2535SAndreas Boehler    return $meta;
60185e2535SAndreas Boehler  }
61185e2535SAndreas Boehler
62185e2535SAndreas Boehler  /**
63185e2535SAndreas Boehler   * Retrieve the meta data for a given page
64185e2535SAndreas Boehler   *
65185e2535SAndreas Boehler   * @param string $id optional The page ID
66185e2535SAndreas Boehler   * @return array with meta data
67185e2535SAndreas Boehler   */
68185e2535SAndreas Boehler  public function getCalendarMetaForPage($id = null)
69185e2535SAndreas Boehler  {
70185e2535SAndreas Boehler      if(is_null($id))
71185e2535SAndreas Boehler      {
72185e2535SAndreas Boehler          global $ID;
73185e2535SAndreas Boehler          $id = $ID;
74185e2535SAndreas Boehler      }
75185e2535SAndreas Boehler
76185e2535SAndreas Boehler      $meta = $this->getMeta($id);
77185e2535SAndreas Boehler      if(isset($meta['plugin_davcal']))
78185e2535SAndreas Boehler        return $meta['plugin_davcal'];
79185e2535SAndreas Boehler      else
80185e2535SAndreas Boehler        return array();
81185e2535SAndreas Boehler  }
82185e2535SAndreas Boehler
83185e2535SAndreas Boehler  /**
84d71c9934SAndreas Boehler   * Check the permission of a user for a given calendar ID
85d71c9934SAndreas Boehler   *
86d71c9934SAndreas Boehler   * @param string $id The calendar ID to check
87d71c9934SAndreas Boehler   * @return int AUTH_* constants
88d71c9934SAndreas Boehler   */
89d71c9934SAndreas Boehler  public function checkCalendarPermission($id)
90d71c9934SAndreas Boehler  {
91d4992453SAndreas Boehler      if(strpos($id, 'webdav://') === 0)
92d71c9934SAndreas Boehler      {
93d71c9934SAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
94d71c9934SAndreas Boehler          if(is_null($wdc))
95d71c9934SAndreas Boehler            return AUTH_NONE;
96d4992453SAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
97d71c9934SAndreas Boehler          $settings = $wdc->getConnection($connectionId);
98d71c9934SAndreas Boehler          if($settings === false)
99d71c9934SAndreas Boehler            return AUTH_NONE;
10058d0e54eSAndreas Boehler          // Check if webdavclient has permissions attached to a page
10158d0e54eSAndreas Boehler          if(!empty($settings['permission']))
10258d0e54eSAndreas Boehler          {
10358d0e54eSAndreas Boehler            $perm = auth_quickaclcheck($settings['permission']);
10458d0e54eSAndreas Boehler            // In case the page has more than read permission, but the
10558d0e54eSAndreas Boehler            // calendar is read-only, we need to modify the permission here.
10658d0e54eSAndreas Boehler            if($perm > AUTH_READ && $settings['write'] == 0)
10758d0e54eSAndreas Boehler              $perm = AUTH_READ;
10858d0e54eSAndreas Boehler            return $perm;
10958d0e54eSAndreas Boehler          }
110d71c9934SAndreas Boehler          if($settings['write'] === '1')
111d71c9934SAndreas Boehler            return AUTH_CREATE;
112d71c9934SAndreas Boehler          return AUTH_READ;
113d71c9934SAndreas Boehler      }
114d71c9934SAndreas Boehler      else
115d71c9934SAndreas Boehler      {
116d71c9934SAndreas Boehler          $calid = $this->getCalendarIdForPage($id);
117d71c9934SAndreas Boehler          // We return AUTH_READ if the calendar does not exist. This makes
118d71c9934SAndreas Boehler          // davcal happy when there are just included calendars
119d71c9934SAndreas Boehler          if($calid === false)
120d71c9934SAndreas Boehler            return AUTH_READ;
121d71c9934SAndreas Boehler          return auth_quickaclcheck($id);
122d71c9934SAndreas Boehler      }
123d71c9934SAndreas Boehler  }
124d71c9934SAndreas Boehler
125d71c9934SAndreas Boehler  /**
12680e1ddf7SAndreas Boehler   * Filter calendar pages and return only those where the current
12780e1ddf7SAndreas Boehler   * user has at least read permission.
12880e1ddf7SAndreas Boehler   *
12980e1ddf7SAndreas Boehler   * @param array $calendarPages Array with calendar pages to check
13080e1ddf7SAndreas Boehler   * @return array with filtered calendar pages
13180e1ddf7SAndreas Boehler   */
13280e1ddf7SAndreas Boehler  public function filterCalendarPagesByUserPermission($calendarPages)
13380e1ddf7SAndreas Boehler  {
13480e1ddf7SAndreas Boehler      $retList = array();
13580e1ddf7SAndreas Boehler      foreach($calendarPages as $page => $data)
13680e1ddf7SAndreas Boehler      {
13758d0e54eSAndreas Boehler          if($this->checkCalendarPermission($page) >= AUTH_READ)
13880e1ddf7SAndreas Boehler          {
13980e1ddf7SAndreas Boehler              $retList[$page] = $data;
14080e1ddf7SAndreas Boehler          }
14180e1ddf7SAndreas Boehler      }
14280e1ddf7SAndreas Boehler      return $retList;
14380e1ddf7SAndreas Boehler  }
14480e1ddf7SAndreas Boehler
14580e1ddf7SAndreas Boehler  /**
146185e2535SAndreas Boehler   * Get all calendar pages used by a given page
147185e2535SAndreas Boehler   * based on the stored metadata
148185e2535SAndreas Boehler   *
149185e2535SAndreas Boehler   * @param string $id optional The page id
150185e2535SAndreas Boehler   * @return mixed The pages as array or false
151185e2535SAndreas Boehler   */
152185e2535SAndreas Boehler  public function getCalendarPagesByMeta($id = null)
153185e2535SAndreas Boehler  {
154185e2535SAndreas Boehler      if(is_null($id))
155185e2535SAndreas Boehler      {
156185e2535SAndreas Boehler          global $ID;
157185e2535SAndreas Boehler          $id = $ID;
158185e2535SAndreas Boehler      }
159185e2535SAndreas Boehler
160185e2535SAndreas Boehler      $meta = $this->getCalendarMetaForPage($id);
1610b805092SAndreas Boehler
162185e2535SAndreas Boehler      if(isset($meta['id']))
163ed764890SAndreas Boehler      {
164ed764890SAndreas Boehler          // Filter the list of pages by permission
16580e1ddf7SAndreas Boehler          $pages = $this->filterCalendarPagesByUserPermission($meta['id']);
16680e1ddf7SAndreas Boehler          if(empty($pages))
167ed764890SAndreas Boehler            return false;
16880e1ddf7SAndreas Boehler          return $pages;
169ed764890SAndreas Boehler      }
170185e2535SAndreas Boehler      return false;
171185e2535SAndreas Boehler  }
172185e2535SAndreas Boehler
173185e2535SAndreas Boehler  /**
174185e2535SAndreas Boehler   * Get a list of calendar names/pages/ids/colors
175185e2535SAndreas Boehler   * for an array of page ids
176185e2535SAndreas Boehler   *
177185e2535SAndreas Boehler   * @param array $calendarPages The calendar pages to retrieve
178185e2535SAndreas Boehler   * @return array The list
179185e2535SAndreas Boehler   */
180185e2535SAndreas Boehler  public function getCalendarMapForIDs($calendarPages)
181185e2535SAndreas Boehler  {
182185e2535SAndreas Boehler      $data = array();
1834a2bf5eeSAndreas Boehler      foreach($calendarPages as $page => $color)
184185e2535SAndreas Boehler      {
1850b805092SAndreas Boehler            if(strpos($page, 'webdav://') === 0)
1860b805092SAndreas Boehler            {
1870b805092SAndreas Boehler                $wdc =& plugin_load('helper', 'webdavclient');
1880b805092SAndreas Boehler                if(is_null($wdc))
1890b805092SAndreas Boehler                    continue;
1900b805092SAndreas Boehler                $connectionId = str_replace('webdav://', '', $page);
1910b805092SAndreas Boehler                $settings = $wdc->getConnection($connectionId);
1922393a702SAndreas Boehler                if($settings === false)
1932393a702SAndreas Boehler                    continue;
1940b805092SAndreas Boehler                $name = $settings['displayname'];
195d71c9934SAndreas Boehler                $write = ($settings['write'] === '1');
1960b805092SAndreas Boehler                $calid = $connectionId;
197cd2f100dSAndreas Boehler                $color = '#3a87ad';
1980b805092SAndreas Boehler            }
1990b805092SAndreas Boehler            else
2000b805092SAndreas Boehler            {
201185e2535SAndreas Boehler                $calid = $this->getCalendarIdForPage($page);
202185e2535SAndreas Boehler                if($calid !== false)
203185e2535SAndreas Boehler                {
204185e2535SAndreas Boehler                    $settings = $this->getCalendarSettings($calid);
205185e2535SAndreas Boehler                    $name = $settings['displayname'];
206cd2f100dSAndreas Boehler                    $color = $settings['calendarcolor'];
207ed764890SAndreas Boehler                    $write = (auth_quickaclcheck($page) > AUTH_READ);
2080b805092SAndreas Boehler                }
2090b805092SAndreas Boehler                else
2100b805092SAndreas Boehler                {
2110b805092SAndreas Boehler                    continue;
2120b805092SAndreas Boehler                }
2130b805092SAndreas Boehler            }
214185e2535SAndreas Boehler            $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid,
215ed764890SAndreas Boehler                            'color' => $color, 'write' => $write);
216185e2535SAndreas Boehler      }
217185e2535SAndreas Boehler      return $data;
218185e2535SAndreas Boehler  }
219185e2535SAndreas Boehler
220185e2535SAndreas Boehler  /**
221185e2535SAndreas Boehler   * Get the saved calendar color for a given page.
222185e2535SAndreas Boehler   *
223185e2535SAndreas Boehler   * @param string $id optional The page ID
224185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
225185e2535SAndreas Boehler   */
226185e2535SAndreas Boehler  public function getCalendarColorForPage($id = null)
227185e2535SAndreas Boehler  {
228185e2535SAndreas Boehler      if(is_null($id))
229185e2535SAndreas Boehler      {
230185e2535SAndreas Boehler          global $ID;
231185e2535SAndreas Boehler          $id = $ID;
232185e2535SAndreas Boehler      }
233185e2535SAndreas Boehler
234185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
235185e2535SAndreas Boehler      if($calid === false)
236185e2535SAndreas Boehler        return false;
237185e2535SAndreas Boehler
238185e2535SAndreas Boehler      return $this->getCalendarColorForCalendar($calid);
239185e2535SAndreas Boehler  }
240185e2535SAndreas Boehler
241185e2535SAndreas Boehler  /**
242185e2535SAndreas Boehler   * Get the saved calendar color for a given calendar ID.
243185e2535SAndreas Boehler   *
244185e2535SAndreas Boehler   * @param string $id optional The calendar ID
245185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
246185e2535SAndreas Boehler   */
247185e2535SAndreas Boehler  public function getCalendarColorForCalendar($calid)
248185e2535SAndreas Boehler  {
249185e2535SAndreas Boehler      if(isset($this->cachedValues['calendarcolor'][$calid]))
250185e2535SAndreas Boehler        return $this->cachedValues['calendarcolor'][$calid];
251185e2535SAndreas Boehler
252185e2535SAndreas Boehler      $row = $this->getCalendarSettings($calid);
253185e2535SAndreas Boehler
254185e2535SAndreas Boehler      if(!isset($row['calendarcolor']))
255185e2535SAndreas Boehler        return false;
256185e2535SAndreas Boehler
257185e2535SAndreas Boehler      $color = $row['calendarcolor'];
258185e2535SAndreas Boehler      $this->cachedValues['calendarcolor'][$calid] = $color;
259185e2535SAndreas Boehler      return $color;
260185e2535SAndreas Boehler  }
261185e2535SAndreas Boehler
262185e2535SAndreas Boehler  /**
263e86c8dd3SAndreas Boehler   * Get the user's principal URL for iOS sync
264e86c8dd3SAndreas Boehler   * @param string $user the user name
265e86c8dd3SAndreas Boehler   * @return the URL to the principal sync
266e86c8dd3SAndreas Boehler   */
267e86c8dd3SAndreas Boehler  public function getPrincipalUrlForUser($user)
268e86c8dd3SAndreas Boehler  {
269e86c8dd3SAndreas Boehler      if(is_null($user))
270e86c8dd3SAndreas Boehler        return false;
271e86c8dd3SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user;
272e86c8dd3SAndreas Boehler      return $url;
273e86c8dd3SAndreas Boehler  }
274e86c8dd3SAndreas Boehler
275e86c8dd3SAndreas Boehler  /**
276185e2535SAndreas Boehler   * Set the calendar color for a given page.
277185e2535SAndreas Boehler   *
278185e2535SAndreas Boehler   * @param string $color The color definition
279185e2535SAndreas Boehler   * @param string $id optional The page ID
280185e2535SAndreas Boehler   * @return boolean True on success, otherwise false
281185e2535SAndreas Boehler   */
282185e2535SAndreas Boehler  public function setCalendarColorForPage($color, $id = null)
283185e2535SAndreas Boehler  {
284185e2535SAndreas Boehler      if(is_null($id))
285185e2535SAndreas Boehler      {
286185e2535SAndreas Boehler          global $ID;
287185e2535SAndreas Boehler          $id = $ID;
288185e2535SAndreas Boehler      }
289185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
290185e2535SAndreas Boehler      if($calid === false)
291185e2535SAndreas Boehler        return false;
292185e2535SAndreas Boehler
2935f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
2945f2c3e2dSAndreas Boehler      if(!$sqlite)
2955f2c3e2dSAndreas Boehler        return false;
29651f4febbSAndreas Boehler      $query = "UPDATE calendars SET calendarcolor = ? ".
29751f4febbSAndreas Boehler               " WHERE id = ?";
2985f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $color, $calid);
299185e2535SAndreas Boehler      if($res !== false)
300185e2535SAndreas Boehler      {
301185e2535SAndreas Boehler        $this->cachedValues['calendarcolor'][$calid] = $color;
302185e2535SAndreas Boehler        return true;
303185e2535SAndreas Boehler      }
304185e2535SAndreas Boehler      return false;
305185e2535SAndreas Boehler  }
306185e2535SAndreas Boehler
307185e2535SAndreas Boehler  /**
308cb71a62aSAndreas Boehler   * Set the calendar name and description for a given page with a given
309cb71a62aSAndreas Boehler   * page id.
310cb71a62aSAndreas Boehler   * If the calendar doesn't exist, the calendar is created!
311cb71a62aSAndreas Boehler   *
312cb71a62aSAndreas Boehler   * @param string  $name The name of the new calendar
313cb71a62aSAndreas Boehler   * @param string  $description The description of the new calendar
314cb71a62aSAndreas Boehler   * @param string  $id (optional) The ID of the page
315cb71a62aSAndreas Boehler   * @param string  $userid The userid of the creating user
316cb71a62aSAndreas Boehler   *
317cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false.
318cb71a62aSAndreas Boehler   */
319a1a3b679SAndreas Boehler  public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
320a1a3b679SAndreas Boehler  {
321a1a3b679SAndreas Boehler      if(is_null($id))
322a1a3b679SAndreas Boehler      {
323a1a3b679SAndreas Boehler          global $ID;
324a1a3b679SAndreas Boehler          $id = $ID;
325a1a3b679SAndreas Boehler      }
326a1a3b679SAndreas Boehler      if(is_null($userid))
32734a47953SAndreas Boehler      {
32834a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
32934a47953SAndreas Boehler        {
330a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
33134a47953SAndreas Boehler        }
33234a47953SAndreas Boehler        else
33334a47953SAndreas Boehler        {
33434a47953SAndreas Boehler          $userid = uniqid('davcal-');
33534a47953SAndreas Boehler        }
33634a47953SAndreas Boehler      }
337a1a3b679SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
338a1a3b679SAndreas Boehler      if($calid === false)
339a1a3b679SAndreas Boehler        return $this->createCalendarForPage($name, $description, $id, $userid);
340a1a3b679SAndreas Boehler
3415f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3425f2c3e2dSAndreas Boehler      if(!$sqlite)
3435f2c3e2dSAndreas Boehler        return false;
34451f4febbSAndreas Boehler      $query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?";
3455f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $name, $description, $calid);
346b269830cSAndreas Boehler      if($res !== false)
347b269830cSAndreas Boehler        return true;
348b269830cSAndreas Boehler      return false;
349a1a3b679SAndreas Boehler  }
350a1a3b679SAndreas Boehler
351cb71a62aSAndreas Boehler  /**
352d5703f5aSAndreas Boehler   * Update a calendar's displayname
353d5703f5aSAndreas Boehler   *
354d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
355d5703f5aSAndreas Boehler   * @param string $name The new calendar name
356d5703f5aSAndreas Boehler   *
357d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
358d5703f5aSAndreas Boehler   */
359d5703f5aSAndreas Boehler  public function updateCalendarName($calid, $name)
360d5703f5aSAndreas Boehler  {
3615f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3625f2c3e2dSAndreas Boehler      if(!$sqlite)
3635f2c3e2dSAndreas Boehler        return false;
364d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET displayname = ? WHERE id = ?";
3655f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $name);
366d5703f5aSAndreas Boehler      if($res !== false)
367d5703f5aSAndreas Boehler      {
368d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
369d5703f5aSAndreas Boehler        return true;
370d5703f5aSAndreas Boehler      }
371d5703f5aSAndreas Boehler      return false;
372d5703f5aSAndreas Boehler  }
373d5703f5aSAndreas Boehler
374d5703f5aSAndreas Boehler  /**
375d5703f5aSAndreas Boehler   * Update the calendar description
376d5703f5aSAndreas Boehler   *
377d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
378d5703f5aSAndreas Boehler   * @param string $description The new calendar's description
379d5703f5aSAndreas Boehler   *
380d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
381d5703f5aSAndreas Boehler   */
382d5703f5aSAndreas Boehler  public function updateCalendarDescription($calid, $description)
383d5703f5aSAndreas Boehler  {
3845f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3855f2c3e2dSAndreas Boehler      if(!$sqlite)
3865f2c3e2dSAndreas Boehler        return false;
387d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET description = ? WHERE id = ?";
3885f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $description);
389d5703f5aSAndreas Boehler      if($res !== false)
390d5703f5aSAndreas Boehler      {
391d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
392d5703f5aSAndreas Boehler        return true;
393d5703f5aSAndreas Boehler      }
394d5703f5aSAndreas Boehler      return false;
395d5703f5aSAndreas Boehler  }
396d5703f5aSAndreas Boehler
397d5703f5aSAndreas Boehler  /**
398d5703f5aSAndreas Boehler   * Update a calendar's timezone information
399d5703f5aSAndreas Boehler   *
400d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
401d5703f5aSAndreas Boehler   * @param string $timezone The new timezone to set
402d5703f5aSAndreas Boehler   *
403d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
404d5703f5aSAndreas Boehler   */
405d5703f5aSAndreas Boehler  public function updateCalendarTimezone($calid, $timezone)
406d5703f5aSAndreas Boehler  {
4075f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
4085f2c3e2dSAndreas Boehler      if(!$sqlite)
4095f2c3e2dSAndreas Boehler        return false;
410d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET timezone = ? WHERE id = ?";
4115f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $timezone);
412d5703f5aSAndreas Boehler      if($res !== false)
413d5703f5aSAndreas Boehler      {
414d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
415d5703f5aSAndreas Boehler        return true;
416d5703f5aSAndreas Boehler      }
417d5703f5aSAndreas Boehler      return false;
418d5703f5aSAndreas Boehler  }
419d5703f5aSAndreas Boehler
420d5703f5aSAndreas Boehler  /**
421cb71a62aSAndreas Boehler   * Save the personal settings to the SQLite database 'calendarsettings'.
422cb71a62aSAndreas Boehler   *
423cb71a62aSAndreas Boehler   * @param array  $settings The settings array to store
424cb71a62aSAndreas Boehler   * @param string $userid (optional) The userid to store
425cb71a62aSAndreas Boehler   *
426cb71a62aSAndreas Boehler   * @param boolean True on success, otherwise false
427cb71a62aSAndreas Boehler   */
428a495d34cSAndreas Boehler  public function savePersonalSettings($settings, $userid = null)
429a495d34cSAndreas Boehler  {
430a495d34cSAndreas Boehler      if(is_null($userid))
43134a47953SAndreas Boehler      {
43234a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
43334a47953SAndreas Boehler          {
434a495d34cSAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
43534a47953SAndreas Boehler          }
43634a47953SAndreas Boehler          else
43734a47953SAndreas Boehler          {
43834a47953SAndreas Boehler              return false;
43934a47953SAndreas Boehler          }
44034a47953SAndreas Boehler      }
4415f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
4425f2c3e2dSAndreas Boehler      if(!$sqlite)
4435f2c3e2dSAndreas Boehler        return false;
4445f2c3e2dSAndreas Boehler      $sqlite->query("BEGIN TRANSACTION");
445a495d34cSAndreas Boehler
44651f4febbSAndreas Boehler      $query = "DELETE FROM calendarsettings WHERE userid = ?";
4475f2c3e2dSAndreas Boehler      $sqlite->query($query, $userid);
448bd883736SAndreas Boehler
449a495d34cSAndreas Boehler      foreach($settings as $key => $value)
450a495d34cSAndreas Boehler      {
45151f4febbSAndreas Boehler          $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)";
4525f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $userid, $key, $value);
453a495d34cSAndreas Boehler          if($res === false)
454a495d34cSAndreas Boehler              return false;
455a495d34cSAndreas Boehler      }
4565f2c3e2dSAndreas Boehler      $sqlite->query("COMMIT TRANSACTION");
457185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
458a495d34cSAndreas Boehler      return true;
459a495d34cSAndreas Boehler  }
460a495d34cSAndreas Boehler
461cb71a62aSAndreas Boehler  /**
462cb71a62aSAndreas Boehler   * Retrieve the settings array for a given user id.
463cb71a62aSAndreas Boehler   * Some sane defaults are returned, currently:
464cb71a62aSAndreas Boehler   *
465cb71a62aSAndreas Boehler   *    timezone    => local
466cb71a62aSAndreas Boehler   *    weeknumbers => 0
467cb71a62aSAndreas Boehler   *    workweek    => 0
468cb71a62aSAndreas Boehler   *
469cb71a62aSAndreas Boehler   * @param string $userid (optional) The user id to retrieve
470cb71a62aSAndreas Boehler   *
471cb71a62aSAndreas Boehler   * @return array The settings array
472cb71a62aSAndreas Boehler   */
473a495d34cSAndreas Boehler  public function getPersonalSettings($userid = null)
474a495d34cSAndreas Boehler  {
475bd883736SAndreas Boehler      // Some sane default settings
476bd883736SAndreas Boehler      $settings = array(
477fb813b30SAndreas Boehler        'timezone' => $this->getConf('timezone'),
478fb813b30SAndreas Boehler        'weeknumbers' => $this->getConf('weeknumbers'),
479fb813b30SAndreas Boehler        'workweek' => $this->getConf('workweek'),
4801d5bdcd0SAndreas Boehler        'monday' => $this->getConf('monday'),
4811d5bdcd0SAndreas Boehler        'timeformat' => $this->getConf('timeformat')
482bd883736SAndreas Boehler      );
48334a47953SAndreas Boehler      if(is_null($userid))
48434a47953SAndreas Boehler      {
48534a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
48634a47953SAndreas Boehler          {
48734a47953SAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
48834a47953SAndreas Boehler          }
48934a47953SAndreas Boehler          else
49034a47953SAndreas Boehler          {
49134a47953SAndreas Boehler            return $settings;
49234a47953SAndreas Boehler          }
49334a47953SAndreas Boehler      }
49434a47953SAndreas Boehler
4955f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
4965f2c3e2dSAndreas Boehler      if(!$sqlite)
4975f2c3e2dSAndreas Boehler        return false;
49834a47953SAndreas Boehler      if(isset($this->cachedValues['settings'][$userid]))
49934a47953SAndreas Boehler        return $this->cachedValues['settings'][$userid];
50051f4febbSAndreas Boehler      $query = "SELECT key, value FROM calendarsettings WHERE userid = ?";
5015f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $userid);
5025f2c3e2dSAndreas Boehler      $arr = $sqlite->res2arr($res);
503a495d34cSAndreas Boehler      foreach($arr as $row)
504a495d34cSAndreas Boehler      {
505a495d34cSAndreas Boehler          $settings[$row['key']] = $row['value'];
506a495d34cSAndreas Boehler      }
507185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
508a495d34cSAndreas Boehler      return $settings;
509a495d34cSAndreas Boehler  }
510a495d34cSAndreas Boehler
511cb71a62aSAndreas Boehler  /**
512cb71a62aSAndreas Boehler   * Retrieve the calendar ID based on a page ID from the SQLite table
513cb71a62aSAndreas Boehler   * 'pagetocalendarmapping'.
514cb71a62aSAndreas Boehler   *
515cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to retrieve the corresponding calendar
516cb71a62aSAndreas Boehler   *
517cb71a62aSAndreas Boehler   * @return mixed the ID on success, otherwise false
518cb71a62aSAndreas Boehler   */
519a1a3b679SAndreas Boehler  public function getCalendarIdForPage($id = null)
520a1a3b679SAndreas Boehler  {
521a1a3b679SAndreas Boehler      if(is_null($id))
522a1a3b679SAndreas Boehler      {
523a1a3b679SAndreas Boehler          global $ID;
524a1a3b679SAndreas Boehler          $id = $ID;
525a1a3b679SAndreas Boehler      }
526a1a3b679SAndreas Boehler
527185e2535SAndreas Boehler      if(isset($this->cachedValues['calid'][$id]))
528185e2535SAndreas Boehler        return $this->cachedValues['calid'][$id];
529185e2535SAndreas Boehler
5305f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
5315f2c3e2dSAndreas Boehler      if(!$sqlite)
5325f2c3e2dSAndreas Boehler        return false;
53351f4febbSAndreas Boehler      $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?";
5345f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $id);
5355f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
536a1a3b679SAndreas Boehler      if(isset($row['calid']))
537185e2535SAndreas Boehler      {
538185e2535SAndreas Boehler        $calid = $row['calid'];
539185e2535SAndreas Boehler        $this->cachedValues['calid'] = $calid;
540185e2535SAndreas Boehler        return $calid;
541185e2535SAndreas Boehler      }
542a1a3b679SAndreas Boehler      return false;
543a1a3b679SAndreas Boehler  }
544a1a3b679SAndreas Boehler
545cb71a62aSAndreas Boehler  /**
546cb71a62aSAndreas Boehler   * Retrieve the complete calendar id to page mapping.
547cb71a62aSAndreas Boehler   * This is necessary to be able to retrieve a list of
548cb71a62aSAndreas Boehler   * calendars for a given user and check the access rights.
549cb71a62aSAndreas Boehler   *
550cb71a62aSAndreas Boehler   * @return array The mapping array
551cb71a62aSAndreas Boehler   */
552a1a3b679SAndreas Boehler  public function getCalendarIdToPageMapping()
553a1a3b679SAndreas Boehler  {
5545f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
5555f2c3e2dSAndreas Boehler      if(!$sqlite)
5565f2c3e2dSAndreas Boehler        return false;
557a1a3b679SAndreas Boehler      $query = "SELECT calid, page FROM pagetocalendarmapping";
5585f2c3e2dSAndreas Boehler      $res = $sqlite->query($query);
5595f2c3e2dSAndreas Boehler      $arr = $sqlite->res2arr($res);
560a1a3b679SAndreas Boehler      return $arr;
561a1a3b679SAndreas Boehler  }
562a1a3b679SAndreas Boehler
563cb71a62aSAndreas Boehler  /**
564cb71a62aSAndreas Boehler   * Retrieve all calendar IDs a given user has access to.
565cb71a62aSAndreas Boehler   * The user is specified by the principalUri, so the
566cb71a62aSAndreas Boehler   * user name is actually split from the URI component.
567cb71a62aSAndreas Boehler   *
568cb71a62aSAndreas Boehler   * Access rights are checked against DokuWiki's ACL
569cb71a62aSAndreas Boehler   * and applied accordingly.
570cb71a62aSAndreas Boehler   *
571cb71a62aSAndreas Boehler   * @param string $principalUri The principal URI to work on
572cb71a62aSAndreas Boehler   *
573cb71a62aSAndreas Boehler   * @return array An associative array of calendar IDs
574cb71a62aSAndreas Boehler   */
575a1a3b679SAndreas Boehler  public function getCalendarIdsForUser($principalUri)
576a1a3b679SAndreas Boehler  {
57734a47953SAndreas Boehler      global $auth;
578a1a3b679SAndreas Boehler      $user = explode('/', $principalUri);
579a1a3b679SAndreas Boehler      $user = end($user);
580a1a3b679SAndreas Boehler      $mapping = $this->getCalendarIdToPageMapping();
581a1a3b679SAndreas Boehler      $calids = array();
58234a47953SAndreas Boehler      $ud = $auth->getUserData($user);
58334a47953SAndreas Boehler      $groups = $ud['grps'];
584a1a3b679SAndreas Boehler      foreach($mapping as $row)
585a1a3b679SAndreas Boehler      {
586a1a3b679SAndreas Boehler          $id = $row['calid'];
58713b16484SAndreas Boehler          $enabled = $this->getCalendarStatus($id);
58813b16484SAndreas Boehler          if($enabled == false)
58913b16484SAndreas Boehler            continue;
590a1a3b679SAndreas Boehler          $page = $row['page'];
59134a47953SAndreas Boehler          $acl = auth_aclcheck($page, $user, $groups);
592a1a3b679SAndreas Boehler          if($acl >= AUTH_READ)
593a1a3b679SAndreas Boehler          {
594a1a3b679SAndreas Boehler              $write = $acl > AUTH_READ;
595a1a3b679SAndreas Boehler              $calids[$id] = array('readonly' => !$write);
596a1a3b679SAndreas Boehler          }
597a1a3b679SAndreas Boehler      }
598a1a3b679SAndreas Boehler      return $calids;
599a1a3b679SAndreas Boehler  }
600a1a3b679SAndreas Boehler
601cb71a62aSAndreas Boehler  /**
602cb71a62aSAndreas Boehler   * Create a new calendar for a given page ID and set name and description
603cb71a62aSAndreas Boehler   * accordingly. Also update the pagetocalendarmapping table on success.
604cb71a62aSAndreas Boehler   *
605cb71a62aSAndreas Boehler   * @param string $name The calendar's name
606cb71a62aSAndreas Boehler   * @param string $description The calendar's description
607cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to work on
608cb71a62aSAndreas Boehler   * @param string $userid (optional) The user ID that created the calendar
609cb71a62aSAndreas Boehler   *
610cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
611cb71a62aSAndreas Boehler   */
612a1a3b679SAndreas Boehler  public function createCalendarForPage($name, $description, $id = null, $userid = null)
613a1a3b679SAndreas Boehler  {
614a1a3b679SAndreas Boehler      if(is_null($id))
615a1a3b679SAndreas Boehler      {
616a1a3b679SAndreas Boehler          global $ID;
617a1a3b679SAndreas Boehler          $id = $ID;
618a1a3b679SAndreas Boehler      }
619a1a3b679SAndreas Boehler      if(is_null($userid))
62034a47953SAndreas Boehler      {
62134a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
62234a47953SAndreas Boehler        {
623a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
62434a47953SAndreas Boehler        }
62534a47953SAndreas Boehler        else
62634a47953SAndreas Boehler        {
62734a47953SAndreas Boehler          $userid = uniqid('davcal-');
62834a47953SAndreas Boehler        }
62934a47953SAndreas Boehler      }
630a1a3b679SAndreas Boehler      $values = array('principals/'.$userid,
631a1a3b679SAndreas Boehler                      $name,
632a1a3b679SAndreas Boehler                      str_replace(array('/', ' ', ':'), '_', $id),
633a1a3b679SAndreas Boehler                      $description,
634a1a3b679SAndreas Boehler                      'VEVENT,VTODO',
63555a741c0SAndreas Boehler                      0,
63655a741c0SAndreas Boehler                      1);
6375f2c3e2dSAndreas Boehler
6385f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
6395f2c3e2dSAndreas Boehler      if(!$sqlite)
6405f2c3e2dSAndreas Boehler        return false;
64151f4febbSAndreas Boehler      $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ".
64251f4febbSAndreas Boehler               "VALUES (?, ?, ?, ?, ?, ?, ?)";
6435f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $values[0], $values[1], $values[2], $values[3], $values[4], $values[5], $values[6]);
64455a741c0SAndreas Boehler      if($res === false)
64555a741c0SAndreas Boehler        return false;
646cb71a62aSAndreas Boehler
647cb71a62aSAndreas Boehler      // Get the new calendar ID
64851f4febbSAndreas Boehler      $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ".
64951f4febbSAndreas Boehler               "uri = ? AND description = ?";
6505f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $values[0], $values[1], $values[2], $values[3]);
6515f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
652cb71a62aSAndreas Boehler
653cb71a62aSAndreas Boehler      // Update the pagetocalendarmapping table with the new calendar ID
654a1a3b679SAndreas Boehler      if(isset($row['id']))
655a1a3b679SAndreas Boehler      {
65651f4febbSAndreas Boehler          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)";
6575f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $id, $row['id']);
65855a741c0SAndreas Boehler          return ($res !== false);
659a1a3b679SAndreas Boehler      }
660a1a3b679SAndreas Boehler
661a1a3b679SAndreas Boehler      return false;
662a1a3b679SAndreas Boehler  }
663a1a3b679SAndreas Boehler
664cb71a62aSAndreas Boehler  /**
665d5703f5aSAndreas Boehler   * Add a new calendar entry to the given calendar. Calendar data is
666d5703f5aSAndreas Boehler   * specified as ICS file, thus it needs to be parsed first.
667d5703f5aSAndreas Boehler   *
668d5703f5aSAndreas Boehler   * This is mainly needed for the sync support.
669d5703f5aSAndreas Boehler   *
670d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
671d5703f5aSAndreas Boehler   * @param string $uri The new object URI
672d5703f5aSAndreas Boehler   * @param string $ics The ICS file
673d5703f5aSAndreas Boehler   *
674d5703f5aSAndreas Boehler   * @return mixed The etag.
675d5703f5aSAndreas Boehler   */
676d5703f5aSAndreas Boehler  public function addCalendarEntryToCalendarByICS($calid, $uri, $ics)
677d5703f5aSAndreas Boehler  {
678d5703f5aSAndreas Boehler    $extraData = $this->getDenormalizedData($ics);
679d5703f5aSAndreas Boehler
6805f2c3e2dSAndreas Boehler    $sqlite = $this->getDB();
6815f2c3e2dSAndreas Boehler    if(!$sqlite)
6825f2c3e2dSAndreas Boehler      return false;
683d5703f5aSAndreas Boehler    $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)";
6845f2c3e2dSAndreas Boehler    $res = $sqlite->query($query,
685d5703f5aSAndreas Boehler            $calid,
686d5703f5aSAndreas Boehler            $uri,
687d5703f5aSAndreas Boehler            $ics,
688d5703f5aSAndreas Boehler            time(),
689d5703f5aSAndreas Boehler            $extraData['etag'],
690d5703f5aSAndreas Boehler            $extraData['size'],
691d5703f5aSAndreas Boehler            $extraData['componentType'],
692d5703f5aSAndreas Boehler            $extraData['firstOccurence'],
693d5703f5aSAndreas Boehler            $extraData['lastOccurence'],
694d5703f5aSAndreas Boehler            $extraData['uid']);
695d5703f5aSAndreas Boehler            // If successfully, update the sync token database
696d5703f5aSAndreas Boehler    if($res !== false)
697d5703f5aSAndreas Boehler    {
698d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, $uri, 'added');
699d5703f5aSAndreas Boehler    }
700d5703f5aSAndreas Boehler    return $extraData['etag'];
701d5703f5aSAndreas Boehler  }
702d5703f5aSAndreas Boehler
703d5703f5aSAndreas Boehler  /**
704d5703f5aSAndreas Boehler   * Edit a calendar entry by providing a new ICS file. This is mainly
705d5703f5aSAndreas Boehler   * needed for the sync support.
706d5703f5aSAndreas Boehler   *
707d5703f5aSAndreas Boehler   * @param int $calid The calendar's IS
708d5703f5aSAndreas Boehler   * @param string $uri The object's URI to modify
709d5703f5aSAndreas Boehler   * @param string $ics The new object's ICS file
710d5703f5aSAndreas Boehler   */
711d5703f5aSAndreas Boehler  public function editCalendarEntryToCalendarByICS($calid, $uri, $ics)
712d5703f5aSAndreas Boehler  {
713d5703f5aSAndreas Boehler      $extraData = $this->getDenormalizedData($ics);
714d5703f5aSAndreas Boehler
7155f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
7165f2c3e2dSAndreas Boehler      if(!$sqlite)
7175f2c3e2dSAndreas Boehler        return false;
718d5703f5aSAndreas Boehler      $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?";
7195f2c3e2dSAndreas Boehler      $res = $sqlite->query($query,
720d5703f5aSAndreas Boehler        $ics,
721d5703f5aSAndreas Boehler        time(),
722d5703f5aSAndreas Boehler        $extraData['etag'],
723d5703f5aSAndreas Boehler        $extraData['size'],
724d5703f5aSAndreas Boehler        $extraData['componentType'],
725d5703f5aSAndreas Boehler        $extraData['firstOccurence'],
726d5703f5aSAndreas Boehler        $extraData['lastOccurence'],
727d5703f5aSAndreas Boehler        $extraData['uid'],
728d5703f5aSAndreas Boehler        $calid,
729d5703f5aSAndreas Boehler        $uri
730d5703f5aSAndreas Boehler      );
731d5703f5aSAndreas Boehler      if($res !== false)
732d5703f5aSAndreas Boehler      {
733d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'modified');
734d5703f5aSAndreas Boehler      }
735d5703f5aSAndreas Boehler      return $extraData['etag'];
736d5703f5aSAndreas Boehler  }
737d5703f5aSAndreas Boehler
738d5703f5aSAndreas Boehler  /**
739cb71a62aSAndreas Boehler   * Add a new iCal entry for a given page, i.e. a given calendar.
740cb71a62aSAndreas Boehler   *
741cb71a62aSAndreas Boehler   * The parameter array needs to contain
742cb71a62aSAndreas Boehler   *   detectedtz       => The timezone as detected by the browser
74382a48dfbSAndreas Boehler   *   currenttz        => The timezone in use by the calendar
744cb71a62aSAndreas Boehler   *   eventfrom        => The event's start date
745cb71a62aSAndreas Boehler   *   eventfromtime    => The event's start time
746cb71a62aSAndreas Boehler   *   eventto          => The event's end date
747cb71a62aSAndreas Boehler   *   eventtotime      => The event's end time
748cb71a62aSAndreas Boehler   *   eventname        => The event's name
749cb71a62aSAndreas Boehler   *   eventdescription => The event's description
750cb71a62aSAndreas Boehler   *
751cb71a62aSAndreas Boehler   * @param string $id The page ID to work on
752cb71a62aSAndreas Boehler   * @param string $user The user who created the calendar
753cb71a62aSAndreas Boehler   * @param string $params A parameter array with values to create
754cb71a62aSAndreas Boehler   *
755cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
756cb71a62aSAndreas Boehler   */
757a1a3b679SAndreas Boehler  public function addCalendarEntryToCalendarForPage($id, $user, $params)
758a1a3b679SAndreas Boehler  {
75982a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
76082a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
76182a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
762a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
763bd883736SAndreas Boehler      else
764bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
765cb71a62aSAndreas Boehler
766cb71a62aSAndreas Boehler      // Retrieve dates from settings
767b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
768b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
769b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
770b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
771cb71a62aSAndreas Boehler
772cb71a62aSAndreas Boehler      // Load SabreDAV
7739bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
774a1a3b679SAndreas Boehler      $vcalendar = new \Sabre\VObject\Component\VCalendar();
775cb71a62aSAndreas Boehler
776cb71a62aSAndreas Boehler      // Add VCalendar, UID and Event Name
777a1a3b679SAndreas Boehler      $event = $vcalendar->add('VEVENT');
778b269830cSAndreas Boehler      $uuid = \Sabre\VObject\UUIDUtil::getUUID();
779b269830cSAndreas Boehler      $event->add('UID', $uuid);
780a1a3b679SAndreas Boehler      $event->summary = $params['eventname'];
781cb71a62aSAndreas Boehler
782cb71a62aSAndreas Boehler      // Add a description if requested
7830eebc909SAndreas Boehler      $description = $params['eventdescription'];
7840eebc909SAndreas Boehler      if($description !== '')
7850eebc909SAndreas Boehler        $event->add('DESCRIPTION', $description);
786cb71a62aSAndreas Boehler
7872b7be5bdSAndreas Boehler      // Add a location if requested
7882b7be5bdSAndreas Boehler      $location = $params['eventlocation'];
7892b7be5bdSAndreas Boehler      if($location !== '')
7902b7be5bdSAndreas Boehler        $event->add('LOCATION', $location);
7912b7be5bdSAndreas Boehler
7924ecb526cSAndreas Boehler      // Add attachments
7934ecb526cSAndreas Boehler      $attachments = $params['attachments'];
79482a48dfbSAndreas Boehler      if(!is_null($attachments))
7954ecb526cSAndreas Boehler        foreach($attachments as $attachment)
7964ecb526cSAndreas Boehler          $event->add('ATTACH', $attachment);
7974ecb526cSAndreas Boehler
798cb71a62aSAndreas Boehler      // Create a timestamp for last modified, created and dtstamp values in UTC
799b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
800b269830cSAndreas Boehler      $event->add('DTSTAMP', $dtStamp);
801b269830cSAndreas Boehler      $event->add('CREATED', $dtStamp);
802b269830cSAndreas Boehler      $event->add('LAST-MODIFIED', $dtStamp);
803cb71a62aSAndreas Boehler
804cb71a62aSAndreas Boehler      // Adjust the start date, based on the given timezone information
805b269830cSAndreas Boehler      $dtStart = new \DateTime();
806a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
807b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
808cb71a62aSAndreas Boehler
809cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
810b269830cSAndreas Boehler      if($params['allday'] != '1')
811b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
812cb71a62aSAndreas Boehler
813cb71a62aSAndreas Boehler      // Adjust the end date, based on the given timezone information
814b269830cSAndreas Boehler      $dtEnd = new \DateTime();
815a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
816b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
817cb71a62aSAndreas Boehler
818cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
819b269830cSAndreas Boehler      if($params['allday'] != '1')
820b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
821cb71a62aSAndreas Boehler
822b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
823b269830cSAndreas Boehler      if($params['allday'] == '1')
824b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
825cb71a62aSAndreas Boehler
826cb71a62aSAndreas Boehler      // Really add Start and End events
827b269830cSAndreas Boehler      $dtStartEv = $event->add('DTSTART', $dtStart);
828b269830cSAndreas Boehler      $dtEndEv = $event->add('DTEND', $dtEnd);
829cb71a62aSAndreas Boehler
830cb71a62aSAndreas Boehler      // Adjust the DATE format for allday events
831b269830cSAndreas Boehler      if($params['allday'] == '1')
832b269830cSAndreas Boehler      {
833b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
834b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
835b269830cSAndreas Boehler      }
836cb71a62aSAndreas Boehler
837809cb0faSAndreas Boehler      $eventStr = $vcalendar->serialize();
838809cb0faSAndreas Boehler
839809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
840809cb0faSAndreas Boehler      {
841809cb0faSAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
842809cb0faSAndreas Boehler          if(is_null($wdc))
843809cb0faSAndreas Boehler            return false;
844809cb0faSAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
845809cb0faSAndreas Boehler          return $wdc->addCalendarEntry($connectionId, $eventStr);
846809cb0faSAndreas Boehler      }
847809cb0faSAndreas Boehler      else
848809cb0faSAndreas Boehler      {
849cb71a62aSAndreas Boehler          // Actually add the values to the database
850a1a3b679SAndreas Boehler          $calid = $this->getCalendarIdForPage($id);
85102eea9b8SAndreas Boehler          $uri = $uri = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics';
8521bb22c2bSAndreas Boehler          $now = new \DateTime();
853a1a3b679SAndreas Boehler
8545f2c3e2dSAndreas Boehler          $sqlite = $this->getDB();
8555f2c3e2dSAndreas Boehler          if(!$sqlite)
8565f2c3e2dSAndreas Boehler            return false;
85751f4febbSAndreas Boehler          $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
8585f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT',
85951f4febbSAndreas Boehler                                      $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(),
86051f4febbSAndreas Boehler                                      strlen($eventStr), md5($eventStr), $uuid);
861cb71a62aSAndreas Boehler
862cb71a62aSAndreas Boehler          // If successfully, update the sync token database
86355a741c0SAndreas Boehler          if($res !== false)
86455a741c0SAndreas Boehler          {
86555a741c0SAndreas Boehler              $this->updateSyncTokenLog($calid, $uri, 'added');
866a1a3b679SAndreas Boehler              return true;
867a1a3b679SAndreas Boehler          }
868809cb0faSAndreas Boehler      }
86955a741c0SAndreas Boehler      return false;
87055a741c0SAndreas Boehler  }
871a1a3b679SAndreas Boehler
872cb71a62aSAndreas Boehler  /**
873cb71a62aSAndreas Boehler   * Retrieve the calendar settings of a given calendar id
874cb71a62aSAndreas Boehler   *
875cb71a62aSAndreas Boehler   * @param string $calid The calendar ID
876cb71a62aSAndreas Boehler   *
877cb71a62aSAndreas Boehler   * @return array The calendar settings array
878cb71a62aSAndreas Boehler   */
879b269830cSAndreas Boehler  public function getCalendarSettings($calid)
880b269830cSAndreas Boehler  {
8815f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
8825f2c3e2dSAndreas Boehler      if(!$sqlite)
8835f2c3e2dSAndreas Boehler        return false;
88413b16484SAndreas Boehler      $query = "SELECT id, principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken, disabled FROM calendars WHERE id= ? ";
8855f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
8865f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
887b269830cSAndreas Boehler      return $row;
888b269830cSAndreas Boehler  }
889b269830cSAndreas Boehler
890cb71a62aSAndreas Boehler  /**
89113b16484SAndreas Boehler   * Retrieve the calendar status of a given calendar id
89213b16484SAndreas Boehler   *
89313b16484SAndreas Boehler   * @param string $calid The calendar ID
89413b16484SAndreas Boehler   * @return boolean True if calendar is enabled, otherwise false
89513b16484SAndreas Boehler   */
89613b16484SAndreas Boehler  public function getCalendarStatus($calid)
89713b16484SAndreas Boehler  {
8985f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
8995f2c3e2dSAndreas Boehler      if(!$sqlite)
9005f2c3e2dSAndreas Boehler        return false;
90113b16484SAndreas Boehler      $query = "SELECT disabled FROM calendars WHERE id = ?";
9025f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
9035f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
90413b16484SAndreas Boehler      if($row['disabled'] == 1)
90513b16484SAndreas Boehler        return false;
90613b16484SAndreas Boehler      else
90713b16484SAndreas Boehler        return true;
90813b16484SAndreas Boehler  }
90913b16484SAndreas Boehler
91013b16484SAndreas Boehler  /**
91113b16484SAndreas Boehler   * Disable a calendar for a given page
91213b16484SAndreas Boehler   *
91313b16484SAndreas Boehler   * @param string $id The page ID
91413b16484SAndreas Boehler   *
91513b16484SAndreas Boehler   * @return boolean true on success, otherwise false
91613b16484SAndreas Boehler   */
91713b16484SAndreas Boehler  public function disableCalendarForPage($id)
91813b16484SAndreas Boehler  {
91913b16484SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
92013b16484SAndreas Boehler      if($calid === false)
92113b16484SAndreas Boehler        return false;
9225f2c3e2dSAndreas Boehler
9235f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9245f2c3e2dSAndreas Boehler      if(!$sqlite)
9255f2c3e2dSAndreas Boehler        return false;
92613b16484SAndreas Boehler      $query = "UPDATE calendars SET disabled = 1 WHERE id = ?";
9275f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
92813b16484SAndreas Boehler      if($res !== false)
92913b16484SAndreas Boehler        return true;
93013b16484SAndreas Boehler      return false;
93113b16484SAndreas Boehler  }
93213b16484SAndreas Boehler
93313b16484SAndreas Boehler  /**
93413b16484SAndreas Boehler   * Enable a calendar for a given page
93513b16484SAndreas Boehler   *
93613b16484SAndreas Boehler   * @param string $id The page ID
93713b16484SAndreas Boehler   *
93813b16484SAndreas Boehler   * @return boolean true on success, otherwise false
93913b16484SAndreas Boehler   */
94013b16484SAndreas Boehler  public function enableCalendarForPage($id)
94113b16484SAndreas Boehler  {
94213b16484SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
94313b16484SAndreas Boehler      if($calid === false)
94413b16484SAndreas Boehler        return false;
9455f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9465f2c3e2dSAndreas Boehler      if(!$sqlite)
9475f2c3e2dSAndreas Boehler        return false;
94813b16484SAndreas Boehler      $query = "UPDATE calendars SET disabled = 0 WHERE id = ?";
9495f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
95013b16484SAndreas Boehler      if($res !== false)
95113b16484SAndreas Boehler        return true;
95213b16484SAndreas Boehler      return false;
95313b16484SAndreas Boehler  }
95413b16484SAndreas Boehler
95513b16484SAndreas Boehler  /**
956cb71a62aSAndreas Boehler   * Retrieve all events that are within a given date range,
957cb71a62aSAndreas Boehler   * based on the timezone setting.
958cb71a62aSAndreas Boehler   *
959cb71a62aSAndreas Boehler   * There is also support for retrieving recurring events,
960cb71a62aSAndreas Boehler   * using Sabre's VObject Iterator. Recurring events are represented
961cb71a62aSAndreas Boehler   * as individual calendar entries with the same UID.
962cb71a62aSAndreas Boehler   *
963cb71a62aSAndreas Boehler   * @param string $id The page ID to work with
964cb71a62aSAndreas Boehler   * @param string $user The user ID to work with
965cb71a62aSAndreas Boehler   * @param string $startDate The start date as a string
966cb71a62aSAndreas Boehler   * @param string $endDate The end date as a string
9674a2bf5eeSAndreas Boehler   * @param string $color (optional) The calendar's color
968cb71a62aSAndreas Boehler   *
969cb71a62aSAndreas Boehler   * @return array An array containing the calendar entries.
970cb71a62aSAndreas Boehler   */
9714a2bf5eeSAndreas Boehler  public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null)
972a1a3b679SAndreas Boehler  {
97382a48dfbSAndreas Boehler      if($timezone !== '' && $timezone !== 'local')
97482a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($timezone);
975bd883736SAndreas Boehler      else
976bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
977a1a3b679SAndreas Boehler      $data = array();
978b8265976SAndreas Boehler      $calname = 'unknown';
979cb71a62aSAndreas Boehler
9805f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9815f2c3e2dSAndreas Boehler      if(!$sqlite)
9825f2c3e2dSAndreas Boehler        return false;
9835f2c3e2dSAndreas Boehler
984a469597cSAndreas Boehler      $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?";
985*7e2d2436Sscottleechua      $params = array($calid);
986a469597cSAndreas Boehler      $startTs = null;
987a469597cSAndreas Boehler      $endTs = null;
988a469597cSAndreas Boehler      if($startDate !== null)
989a469597cSAndreas Boehler      {
990a1a3b679SAndreas Boehler        $startTs = new \DateTime($startDate);
991*7e2d2436Sscottleechua        $query .= " AND lastoccurence > ?";
992*7e2d2436Sscottleechua        $params[] = $startTs->getTimestamp();
993a469597cSAndreas Boehler      }
994a469597cSAndreas Boehler      if($endDate !== null)
995a469597cSAndreas Boehler      {
996a1a3b679SAndreas Boehler        $endTs = new \DateTime($endDate);
997*7e2d2436Sscottleechua        $query .= " AND firstoccurence < ?";
998*7e2d2436Sscottleechua        $params[] = $endTs->getTimestamp();
999a469597cSAndreas Boehler      }
1000cb71a62aSAndreas Boehler
10010b805092SAndreas Boehler      // Load SabreDAV
10020b805092SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
10030b805092SAndreas Boehler
10040b805092SAndreas Boehler      if(strpos($id, 'webdav://') === 0)
10050b805092SAndreas Boehler      {
10060b805092SAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
10070b805092SAndreas Boehler          if(is_null($wdc))
10080b805092SAndreas Boehler            return $data;
10090b805092SAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
10100b805092SAndreas Boehler          $arr = $wdc->getCalendarEntries($connectionId, $startDate, $endDate);
1011b8265976SAndreas Boehler          $conn = $wdc->getConnection($connectionId);
1012b8265976SAndreas Boehler          $calname = $conn['displayname'];
10130b805092SAndreas Boehler      }
10140b805092SAndreas Boehler      else
10150b805092SAndreas Boehler      {
10160b805092SAndreas Boehler          $calid = $this->getCalendarIdForPage($id);
10170b805092SAndreas Boehler          if(is_null($color))
10180b805092SAndreas Boehler            $color = $this->getCalendarColorForCalendar($calid);
10190b805092SAndreas Boehler
102059b68239SAndreas Boehler          $enabled = $this->getCalendarStatus($calid);
102159b68239SAndreas Boehler          if($enabled === false)
102259b68239SAndreas Boehler            return $data;
102359b68239SAndreas Boehler
1024b8265976SAndreas Boehler          $settings = $this->getCalendarSettings($calid);
1025b8265976SAndreas Boehler          $calname = $settings['displayname'];
1026b8265976SAndreas Boehler
1027cb71a62aSAndreas Boehler          // Retrieve matching calendar objects
1028*7e2d2436Sscottleechua          $res = $sqlite->query($query, $params);
10295f2c3e2dSAndreas Boehler          $arr = $sqlite->res2arr($res);
10300b805092SAndreas Boehler      }
1031cb71a62aSAndreas Boehler
1032cb71a62aSAndreas Boehler      // Parse individual calendar entries
1033a1a3b679SAndreas Boehler      foreach($arr as $row)
1034a1a3b679SAndreas Boehler      {
1035a1a3b679SAndreas Boehler          if(isset($row['calendardata']))
1036a1a3b679SAndreas Boehler          {
1037b269830cSAndreas Boehler              $entry = array();
1038a1a3b679SAndreas Boehler              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
1039ebc4eb57SAndreas Boehler              $recurrence = $vcal->VEVENT->RRULE;
1040cb71a62aSAndreas Boehler              // If it is a recurring event, pass it through Sabre's EventIterator
1041ebc4eb57SAndreas Boehler              if($recurrence != null)
1042ebc4eb57SAndreas Boehler              {
1043ebc4eb57SAndreas Boehler                  $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT));
1044ebc4eb57SAndreas Boehler                  $rEvents->rewind();
1045e9b7d302SAndreas Boehler                  while($rEvents->valid())
1046ebc4eb57SAndreas Boehler                  {
1047ebc4eb57SAndreas Boehler                      $event = $rEvents->getEventObject();
1048cb71a62aSAndreas Boehler                      // If we are after the given time range, exit
1049a469597cSAndreas Boehler                      if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()))
1050e9b7d302SAndreas Boehler                          break;
1051cb71a62aSAndreas Boehler
1052cb71a62aSAndreas Boehler                      // If we are before the given time range, continue
1053a469597cSAndreas Boehler                      if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()))
1054ebc4eb57SAndreas Boehler                      {
1055ebc4eb57SAndreas Boehler                          $rEvents->next();
1056ebc4eb57SAndreas Boehler                          continue;
1057ebc4eb57SAndreas Boehler                      }
1058cb71a62aSAndreas Boehler
1059cb71a62aSAndreas Boehler                      // If we are within the given time range, parse the event
1060b8265976SAndreas Boehler                      $data[] = array_merge(array('calendarname' => $calname),
1061b8265976SAndreas Boehler                                            $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true));
1062ebc4eb57SAndreas Boehler                      $rEvents->next();
1063ebc4eb57SAndreas Boehler                  }
1064ebc4eb57SAndreas Boehler              }
1065ebc4eb57SAndreas Boehler              else
1066b8265976SAndreas Boehler                $data[] = array_merge(array('calendarname' => $calname),
1067b8265976SAndreas Boehler                                      $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color));
1068ebc4eb57SAndreas Boehler          }
1069ebc4eb57SAndreas Boehler      }
1070ebc4eb57SAndreas Boehler      return $data;
1071ebc4eb57SAndreas Boehler  }
1072ebc4eb57SAndreas Boehler
1073cb71a62aSAndreas Boehler  /**
1074cb71a62aSAndreas Boehler   * Helper function that parses the iCal data of a VEVENT to a calendar entry.
1075cb71a62aSAndreas Boehler   *
1076cb71a62aSAndreas Boehler   * @param \Sabre\VObject\VEvent $event The event to parse
1077cb71a62aSAndreas Boehler   * @param \DateTimeZone $timezone The timezone object
1078cb71a62aSAndreas Boehler   * @param string $uid The entry's UID
10793c86dda8SAndreas Boehler   * @param boolean $recurring (optional) Set to true to define a recurring event
1080cb71a62aSAndreas Boehler   *
1081cb71a62aSAndreas Boehler   * @return array The parse calendar entry
1082cb71a62aSAndreas Boehler   */
1083185e2535SAndreas Boehler  private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false)
1084ebc4eb57SAndreas Boehler  {
1085ebc4eb57SAndreas Boehler      $entry = array();
1086ebc4eb57SAndreas Boehler      $start = $event->DTSTART;
1087cb71a62aSAndreas Boehler      // Parse only if the start date/time is present
1088b269830cSAndreas Boehler      if($start !== null)
1089b269830cSAndreas Boehler      {
1090b269830cSAndreas Boehler        $dtStart = $start->getDateTime();
1091b269830cSAndreas Boehler        $dtStart->setTimezone($timezone);
1092bf0ad2b4SAndreas Boehler
1093bf0ad2b4SAndreas Boehler        // moment.js doesn't like times be given even if
1094bf0ad2b4SAndreas Boehler        // allDay is set to true
1095bf0ad2b4SAndreas Boehler        // This should fix T23
1096b269830cSAndreas Boehler        if($start['VALUE'] == 'DATE')
1097bf0ad2b4SAndreas Boehler        {
1098b269830cSAndreas Boehler          $entry['allDay'] = true;
1099bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format("Y-m-d");
1100bf0ad2b4SAndreas Boehler        }
1101b269830cSAndreas Boehler        else
1102bf0ad2b4SAndreas Boehler        {
1103b269830cSAndreas Boehler          $entry['allDay'] = false;
1104bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format(\DateTime::ATOM);
1105bf0ad2b4SAndreas Boehler        }
1106b269830cSAndreas Boehler      }
1107ebc4eb57SAndreas Boehler      $end = $event->DTEND;
1108bf0ad2b4SAndreas Boehler      // Parse only if the end date/time is present
1109b269830cSAndreas Boehler      if($end !== null)
1110b269830cSAndreas Boehler      {
1111b269830cSAndreas Boehler        $dtEnd = $end->getDateTime();
1112b269830cSAndreas Boehler        $dtEnd->setTimezone($timezone);
1113bf0ad2b4SAndreas Boehler        if($end['VALUE'] == 'DATE')
1114bf0ad2b4SAndreas Boehler          $entry['end'] = $dtEnd->format("Y-m-d");
1115bf0ad2b4SAndreas Boehler        else
1116b269830cSAndreas Boehler          $entry['end'] = $dtEnd->format(\DateTime::ATOM);
1117b269830cSAndreas Boehler      }
1118954f1ffaSAndreas Boehler      $duration = $event->DURATION;
1119954f1ffaSAndreas Boehler      // Parse duration only if start is set, but end is missing
1120954f1ffaSAndreas Boehler      if($start !== null && $end == null && $duration !== null)
1121954f1ffaSAndreas Boehler      {
1122954f1ffaSAndreas Boehler          $interval = $duration->getDateInterval();
1123954f1ffaSAndreas Boehler          $dtStart = $start->getDateTime();
1124954f1ffaSAndreas Boehler          $dtStart->setTimezone($timezone);
1125954f1ffaSAndreas Boehler          $dtEnd = $dtStart->add($interval);
1126954f1ffaSAndreas Boehler          $dtEnd->setTimezone($timezone);
1127954f1ffaSAndreas Boehler          $entry['end'] = $dtEnd->format(\DateTime::ATOM);
1128954f1ffaSAndreas Boehler      }
1129ebc4eb57SAndreas Boehler      $description = $event->DESCRIPTION;
11300eebc909SAndreas Boehler      if($description !== null)
11310eebc909SAndreas Boehler        $entry['description'] = (string)$description;
11320eebc909SAndreas Boehler      else
11330eebc909SAndreas Boehler        $entry['description'] = '';
11344ecb526cSAndreas Boehler      $attachments = $event->ATTACH;
11354ecb526cSAndreas Boehler      if($attachments !== null)
11364ecb526cSAndreas Boehler      {
11374ecb526cSAndreas Boehler        $entry['attachments'] = array();
11384ecb526cSAndreas Boehler        foreach($attachments as $attachment)
11394ecb526cSAndreas Boehler          $entry['attachments'][] = (string)$attachment;
11404ecb526cSAndreas Boehler      }
1141ebc4eb57SAndreas Boehler      $entry['title'] = (string)$event->summary;
11422b7be5bdSAndreas Boehler      $entry['location'] = (string)$event->location;
1143ebc4eb57SAndreas Boehler      $entry['id'] = $uid;
1144185e2535SAndreas Boehler      $entry['page'] = $page;
1145185e2535SAndreas Boehler      $entry['color'] = $color;
11463c86dda8SAndreas Boehler      $entry['recurring'] = $recurring;
1147185e2535SAndreas Boehler
1148ebc4eb57SAndreas Boehler      return $entry;
1149a1a3b679SAndreas Boehler  }
1150a1a3b679SAndreas Boehler
1151cb71a62aSAndreas Boehler  /**
1152cb71a62aSAndreas Boehler   * Retrieve an event by its UID
1153cb71a62aSAndreas Boehler   *
1154cb71a62aSAndreas Boehler   * @param string $uid The event's UID
1155cb71a62aSAndreas Boehler   *
1156cb71a62aSAndreas Boehler   * @return mixed The table row with the given event
1157cb71a62aSAndreas Boehler   */
1158a1a3b679SAndreas Boehler  public function getEventWithUid($uid)
1159a1a3b679SAndreas Boehler  {
11605f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11615f2c3e2dSAndreas Boehler      if(!$sqlite)
11625f2c3e2dSAndreas Boehler        return false;
116351f4febbSAndreas Boehler      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid = ?";
11645f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $uid);
11655f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
1166a1a3b679SAndreas Boehler      return $row;
1167a1a3b679SAndreas Boehler  }
1168a1a3b679SAndreas Boehler
1169cb71a62aSAndreas Boehler  /**
1170d5703f5aSAndreas Boehler   * Retrieve information of a calendar's object, not including the actual
117159b68239SAndreas Boehler   * calendar data! This is mainly needed for the sync support.
1172d5703f5aSAndreas Boehler   *
1173d5703f5aSAndreas Boehler   * @param int $calid The calendar ID
1174d5703f5aSAndreas Boehler   *
1175d5703f5aSAndreas Boehler   * @return mixed The result
1176d5703f5aSAndreas Boehler   */
1177d5703f5aSAndreas Boehler  public function getCalendarObjects($calid)
1178d5703f5aSAndreas Boehler  {
11795f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11805f2c3e2dSAndreas Boehler      if(!$sqlite)
11815f2c3e2dSAndreas Boehler        return false;
1182d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM calendarobjects WHERE calendarid = ?";
11835f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
11845f2c3e2dSAndreas Boehler      $arr = $sqlite->res2arr($res);
1185d5703f5aSAndreas Boehler      return $arr;
1186d5703f5aSAndreas Boehler  }
1187d5703f5aSAndreas Boehler
1188d5703f5aSAndreas Boehler  /**
1189d5703f5aSAndreas Boehler   * Retrieve a single calendar object by calendar ID and URI
1190d5703f5aSAndreas Boehler   *
1191d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1192d5703f5aSAndreas Boehler   * @param string $uri The object's URI
1193d5703f5aSAndreas Boehler   *
1194d5703f5aSAndreas Boehler   * @return mixed The result
1195d5703f5aSAndreas Boehler   */
1196d5703f5aSAndreas Boehler  public function getCalendarObjectByUri($calid, $uri)
1197d5703f5aSAndreas Boehler  {
11985f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11995f2c3e2dSAndreas Boehler      if(!$sqlite)
12005f2c3e2dSAndreas Boehler        return false;
1201d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri = ?";
12025f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $uri);
12035f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
1204d5703f5aSAndreas Boehler      return $row;
1205d5703f5aSAndreas Boehler  }
1206d5703f5aSAndreas Boehler
1207d5703f5aSAndreas Boehler  /**
1208d5703f5aSAndreas Boehler   * Retrieve several calendar objects by specifying an array of URIs.
1209d5703f5aSAndreas Boehler   * This is mainly neede for sync.
1210d5703f5aSAndreas Boehler   *
1211d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1212d5703f5aSAndreas Boehler   * @param array $uris An array of URIs
1213d5703f5aSAndreas Boehler   *
1214d5703f5aSAndreas Boehler   * @return mixed The result
1215d5703f5aSAndreas Boehler   */
1216d5703f5aSAndreas Boehler  public function getMultipleCalendarObjectsByUri($calid, $uris)
1217d5703f5aSAndreas Boehler  {
12185f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
12195f2c3e2dSAndreas Boehler      if(!$sqlite)
12205f2c3e2dSAndreas Boehler        return false;
1221d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri IN (";
1222d5703f5aSAndreas Boehler      // Inserting a whole bunch of question marks
1223d5703f5aSAndreas Boehler      $query .= implode(',', array_fill(0, count($uris), '?'));
1224d5703f5aSAndreas Boehler      $query .= ')';
1225d5703f5aSAndreas Boehler      $vals = array_merge(array($calid), $uris);
1226d5703f5aSAndreas Boehler
12275f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $vals);
12285f2c3e2dSAndreas Boehler      $arr = $sqlite->res2arr($res);
1229d5703f5aSAndreas Boehler      return $arr;
1230d5703f5aSAndreas Boehler  }
1231d5703f5aSAndreas Boehler
1232d5703f5aSAndreas Boehler  /**
1233cb71a62aSAndreas Boehler   * Retrieve all calendar events for a given calendar ID
1234cb71a62aSAndreas Boehler   *
1235cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
1236cb71a62aSAndreas Boehler   *
1237cb71a62aSAndreas Boehler   * @return array An array containing all calendar data
1238cb71a62aSAndreas Boehler   */
1239f69bb449SAndreas Boehler  public function getAllCalendarEvents($calid)
1240f69bb449SAndreas Boehler  {
124159b68239SAndreas Boehler      $enabled = $this->getCalendarStatus($calid);
124259b68239SAndreas Boehler      if($enabled === false)
124359b68239SAndreas Boehler        return false;
12445f2c3e2dSAndreas Boehler
12455f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
12465f2c3e2dSAndreas Boehler      if(!$sqlite)
12475f2c3e2dSAndreas Boehler        return false;
12487e0b8590SAndreas Boehler      $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?";
12495f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
12505f2c3e2dSAndreas Boehler      $arr = $sqlite->res2arr($res);
1251f69bb449SAndreas Boehler      return $arr;
1252f69bb449SAndreas Boehler  }
1253f69bb449SAndreas Boehler
1254cb71a62aSAndreas Boehler  /**
1255cb71a62aSAndreas Boehler   * Edit a calendar entry for a page, given by its parameters.
1256cb71a62aSAndreas Boehler   * The params array has the same format as @see addCalendarEntryForPage
1257cb71a62aSAndreas Boehler   *
1258cb71a62aSAndreas Boehler   * @param string $id The page's ID to work on
1259cb71a62aSAndreas Boehler   * @param string $user The user's ID to work on
1260cb71a62aSAndreas Boehler   * @param array $params The parameter array for the edited calendar event
1261cb71a62aSAndreas Boehler   *
1262cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1263cb71a62aSAndreas Boehler   */
1264a1a3b679SAndreas Boehler  public function editCalendarEntryForPage($id, $user, $params)
1265a1a3b679SAndreas Boehler  {
126682a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
126782a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
126882a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
1269a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
1270bd883736SAndreas Boehler      else
1271bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
1272cb71a62aSAndreas Boehler
1273cb71a62aSAndreas Boehler      // Parse dates
1274b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
1275b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
1276b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
1277b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
1278cb71a62aSAndreas Boehler
1279cb71a62aSAndreas Boehler      // Retrieve the existing event based on the UID
128055a741c0SAndreas Boehler      $uid = $params['uid'];
1281809cb0faSAndreas Boehler
1282809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1283809cb0faSAndreas Boehler      {
1284809cb0faSAndreas Boehler        $wdc =& plugin_load('helper', 'webdavclient');
1285809cb0faSAndreas Boehler        if(is_null($wdc))
1286809cb0faSAndreas Boehler          return false;
1287809cb0faSAndreas Boehler        $event = $wdc->getCalendarEntryByUid($uid);
1288809cb0faSAndreas Boehler      }
1289809cb0faSAndreas Boehler      else
1290809cb0faSAndreas Boehler      {
129155a741c0SAndreas Boehler        $event = $this->getEventWithUid($uid);
1292809cb0faSAndreas Boehler      }
1293cb71a62aSAndreas Boehler
1294cb71a62aSAndreas Boehler      // Load SabreDAV
12959bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1296a1a3b679SAndreas Boehler      if(!isset($event['calendardata']))
1297a1a3b679SAndreas Boehler        return false;
129855a741c0SAndreas Boehler      $uri = $event['uri'];
129955a741c0SAndreas Boehler      $calid = $event['calendarid'];
1300cb71a62aSAndreas Boehler
1301cb71a62aSAndreas Boehler      // Parse the existing event
1302a1a3b679SAndreas Boehler      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
1303b269830cSAndreas Boehler      $vevent = $vcal->VEVENT;
1304cb71a62aSAndreas Boehler
1305cb71a62aSAndreas Boehler      // Set the new event values
1306b269830cSAndreas Boehler      $vevent->summary = $params['eventname'];
1307b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
13080eebc909SAndreas Boehler      $description = $params['eventdescription'];
13092b7be5bdSAndreas Boehler      $location = $params['eventlocation'];
1310cb71a62aSAndreas Boehler
1311cb71a62aSAndreas Boehler      // Remove existing timestamps to overwrite them
13120eebc909SAndreas Boehler      $vevent->remove('DESCRIPTION');
1313b269830cSAndreas Boehler      $vevent->remove('DTSTAMP');
1314b269830cSAndreas Boehler      $vevent->remove('LAST-MODIFIED');
13154ecb526cSAndreas Boehler      $vevent->remove('ATTACH');
13162b7be5bdSAndreas Boehler      $vevent->remove('LOCATION');
1317cb71a62aSAndreas Boehler
13182b7be5bdSAndreas Boehler      // Add new time stamps, description and location
1319b269830cSAndreas Boehler      $vevent->add('DTSTAMP', $dtStamp);
1320b269830cSAndreas Boehler      $vevent->add('LAST-MODIFIED', $dtStamp);
13210eebc909SAndreas Boehler      if($description !== '')
13220eebc909SAndreas Boehler        $vevent->add('DESCRIPTION', $description);
13232b7be5bdSAndreas Boehler      if($location !== '')
13242b7be5bdSAndreas Boehler        $vevent->add('LOCATION', $location);
1325cb71a62aSAndreas Boehler
13264ecb526cSAndreas Boehler      // Add attachments
13274ecb526cSAndreas Boehler      $attachments = $params['attachments'];
132882a48dfbSAndreas Boehler      if(!is_null($attachments))
13294ecb526cSAndreas Boehler        foreach($attachments as $attachment)
13304ecb526cSAndreas Boehler          $vevent->add('ATTACH', $attachment);
13314ecb526cSAndreas Boehler
1332cb71a62aSAndreas Boehler      // Setup DTSTART
1333b269830cSAndreas Boehler      $dtStart = new \DateTime();
1334a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
1335b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
1336b269830cSAndreas Boehler      if($params['allday'] != '1')
1337b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
1338cb71a62aSAndreas Boehler
13394ecb526cSAndreas Boehler      // Setup DTEND
1340b269830cSAndreas Boehler      $dtEnd = new \DateTime();
1341a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
1342b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
1343b269830cSAndreas Boehler      if($params['allday'] != '1')
1344b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
1345cb71a62aSAndreas Boehler
1346b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
1347b269830cSAndreas Boehler      if($params['allday'] == '1')
1348b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
1349b269830cSAndreas Boehler      $vevent->remove('DTSTART');
1350b269830cSAndreas Boehler      $vevent->remove('DTEND');
1351b269830cSAndreas Boehler      $dtStartEv = $vevent->add('DTSTART', $dtStart);
1352b269830cSAndreas Boehler      $dtEndEv = $vevent->add('DTEND', $dtEnd);
1353cb71a62aSAndreas Boehler
1354cb71a62aSAndreas Boehler      // Remove the time for allday events
1355b269830cSAndreas Boehler      if($params['allday'] == '1')
1356b269830cSAndreas Boehler      {
1357b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
1358b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
1359b269830cSAndreas Boehler      }
1360a1a3b679SAndreas Boehler      $eventStr = $vcal->serialize();
1361809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1362809cb0faSAndreas Boehler      {
1363809cb0faSAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
1364809cb0faSAndreas Boehler          return $wdc->editCalendarEntry($connectionId, $uid, $eventStr);
1365809cb0faSAndreas Boehler      }
1366809cb0faSAndreas Boehler      else
1367809cb0faSAndreas Boehler      {
13685f2c3e2dSAndreas Boehler          $sqlite = $this->getDB();
13695f2c3e2dSAndreas Boehler          if(!$sqlite)
13705f2c3e2dSAndreas Boehler            return false;
1371809cb0faSAndreas Boehler          $now = new DateTime();
1372cb71a62aSAndreas Boehler          // Actually write to the database
137351f4febbSAndreas Boehler          $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ".
137451f4febbSAndreas Boehler                   "firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?";
13755f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(),
137651f4febbSAndreas Boehler                                      $dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid);
137755a741c0SAndreas Boehler          if($res !== false)
137855a741c0SAndreas Boehler          {
137955a741c0SAndreas Boehler              $this->updateSyncTokenLog($calid, $uri, 'modified');
1380a1a3b679SAndreas Boehler              return true;
1381a1a3b679SAndreas Boehler          }
1382809cb0faSAndreas Boehler      }
138355a741c0SAndreas Boehler      return false;
138455a741c0SAndreas Boehler  }
1385a1a3b679SAndreas Boehler
1386cb71a62aSAndreas Boehler  /**
1387d5703f5aSAndreas Boehler   * Delete an event from a calendar by calendar ID and URI
1388d5703f5aSAndreas Boehler   *
1389d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1390d5703f5aSAndreas Boehler   * @param string $uri The object's URI
1391d5703f5aSAndreas Boehler   *
1392d5703f5aSAndreas Boehler   * @return true
1393d5703f5aSAndreas Boehler   */
1394d5703f5aSAndreas Boehler  public function deleteCalendarEntryForCalendarByUri($calid, $uri)
1395d5703f5aSAndreas Boehler  {
13965f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
13975f2c3e2dSAndreas Boehler      if(!$sqlite)
13985f2c3e2dSAndreas Boehler        return false;
1399d5703f5aSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?";
14005f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $uri);
1401d5703f5aSAndreas Boehler      if($res !== false)
1402d5703f5aSAndreas Boehler      {
1403d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
1404d5703f5aSAndreas Boehler      }
1405d5703f5aSAndreas Boehler      return true;
1406d5703f5aSAndreas Boehler  }
1407d5703f5aSAndreas Boehler
1408d5703f5aSAndreas Boehler  /**
1409cb71a62aSAndreas Boehler   * Delete a calendar entry for a given page. Actually, the event is removed
1410cb71a62aSAndreas Boehler   * based on the entry's UID, so that page ID is no used.
1411cb71a62aSAndreas Boehler   *
1412cb71a62aSAndreas Boehler   * @param string $id The page's ID (unused)
1413cb71a62aSAndreas Boehler   * @param array $params The parameter array to work with
1414cb71a62aSAndreas Boehler   *
1415cb71a62aSAndreas Boehler   * @return boolean True
1416cb71a62aSAndreas Boehler   */
1417a1a3b679SAndreas Boehler  public function deleteCalendarEntryForPage($id, $params)
1418a1a3b679SAndreas Boehler  {
1419a1a3b679SAndreas Boehler      $uid = $params['uid'];
1420809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1421809cb0faSAndreas Boehler      {
1422809cb0faSAndreas Boehler        $wdc =& plugin_load('helper', 'webdavclient');
1423809cb0faSAndreas Boehler        if(is_null($wdc))
1424809cb0faSAndreas Boehler          return false;
1425809cb0faSAndreas Boehler        $connectionId = str_replace('webdav://', '', $id);
1426809cb0faSAndreas Boehler        $result = $wdc->deleteCalendarEntry($connectionId, $uid);
1427809cb0faSAndreas Boehler        return $result;
1428809cb0faSAndreas Boehler      }
14295f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
14305f2c3e2dSAndreas Boehler      if(!$sqlite)
14315f2c3e2dSAndreas Boehler        return false;
143255a741c0SAndreas Boehler      $event = $this->getEventWithUid($uid);
14332c14b82bSAndreas Boehler      $calid = $event['calendarid'];
143455a741c0SAndreas Boehler      $uri = $event['uri'];
143551f4febbSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE uid = ?";
14365f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $uid);
143755a741c0SAndreas Boehler      if($res !== false)
143855a741c0SAndreas Boehler      {
143955a741c0SAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
144055a741c0SAndreas Boehler      }
1441a1a3b679SAndreas Boehler      return true;
1442a1a3b679SAndreas Boehler  }
1443a1a3b679SAndreas Boehler
1444cb71a62aSAndreas Boehler  /**
1445cb71a62aSAndreas Boehler   * Retrieve the current sync token for a calendar
1446cb71a62aSAndreas Boehler   *
1447cb71a62aSAndreas Boehler   * @param string $calid The calendar id
1448cb71a62aSAndreas Boehler   *
1449cb71a62aSAndreas Boehler   * @return mixed The synctoken or false
1450cb71a62aSAndreas Boehler   */
145155a741c0SAndreas Boehler  public function getSyncTokenForCalendar($calid)
145255a741c0SAndreas Boehler  {
1453b269830cSAndreas Boehler      $row = $this->getCalendarSettings($calid);
145455a741c0SAndreas Boehler      if(isset($row['synctoken']))
145555a741c0SAndreas Boehler          return $row['synctoken'];
145655a741c0SAndreas Boehler      return false;
145755a741c0SAndreas Boehler  }
145855a741c0SAndreas Boehler
1459cb71a62aSAndreas Boehler  /**
1460cb71a62aSAndreas Boehler   * Helper function to convert the operation name to
1461cb71a62aSAndreas Boehler   * an operation code as stored in the database
1462cb71a62aSAndreas Boehler   *
1463cb71a62aSAndreas Boehler   * @param string $operationName The operation name
1464cb71a62aSAndreas Boehler   *
1465cb71a62aSAndreas Boehler   * @return mixed The operation code or false
1466cb71a62aSAndreas Boehler   */
146755a741c0SAndreas Boehler  public function operationNameToOperation($operationName)
146855a741c0SAndreas Boehler  {
146955a741c0SAndreas Boehler      switch($operationName)
147055a741c0SAndreas Boehler      {
147155a741c0SAndreas Boehler          case 'added':
147255a741c0SAndreas Boehler              return 1;
147355a741c0SAndreas Boehler          break;
147455a741c0SAndreas Boehler          case 'modified':
147555a741c0SAndreas Boehler              return 2;
147655a741c0SAndreas Boehler          break;
147755a741c0SAndreas Boehler          case 'deleted':
147855a741c0SAndreas Boehler              return 3;
147955a741c0SAndreas Boehler          break;
148055a741c0SAndreas Boehler      }
148155a741c0SAndreas Boehler      return false;
148255a741c0SAndreas Boehler  }
148355a741c0SAndreas Boehler
1484cb71a62aSAndreas Boehler  /**
1485cb71a62aSAndreas Boehler   * Update the sync token log based on the calendar id and the
1486cb71a62aSAndreas Boehler   * operation that was performed.
1487cb71a62aSAndreas Boehler   *
1488cb71a62aSAndreas Boehler   * @param string $calid The calendar ID that was modified
1489cb71a62aSAndreas Boehler   * @param string $uri The calendar URI that was modified
1490cb71a62aSAndreas Boehler   * @param string $operation The operation that was performed
1491cb71a62aSAndreas Boehler   *
1492cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1493cb71a62aSAndreas Boehler   */
149455a741c0SAndreas Boehler  private function updateSyncTokenLog($calid, $uri, $operation)
149555a741c0SAndreas Boehler  {
149655a741c0SAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
149755a741c0SAndreas Boehler      $operationCode = $this->operationNameToOperation($operation);
149855a741c0SAndreas Boehler      if(($operationCode === false) || ($currentToken === false))
149955a741c0SAndreas Boehler          return false;
150055a741c0SAndreas Boehler      $values = array($uri,
150155a741c0SAndreas Boehler                      $currentToken,
150255a741c0SAndreas Boehler                      $calid,
150355a741c0SAndreas Boehler                      $operationCode
150455a741c0SAndreas Boehler      );
15055f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
15065f2c3e2dSAndreas Boehler      if(!$sqlite)
15075f2c3e2dSAndreas Boehler        return false;
150851f4febbSAndreas Boehler      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)";
15095f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $uri, $currentToken, $calid, $operationCode);
151055a741c0SAndreas Boehler      if($res === false)
151155a741c0SAndreas Boehler        return false;
151255a741c0SAndreas Boehler      $currentToken++;
151351f4febbSAndreas Boehler      $query = "UPDATE calendars SET synctoken = ? WHERE id = ?";
15145f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $currentToken, $calid);
151555a741c0SAndreas Boehler      return ($res !== false);
151655a741c0SAndreas Boehler  }
151755a741c0SAndreas Boehler
1518cb71a62aSAndreas Boehler  /**
1519cb71a62aSAndreas Boehler   * Return the sync URL for a given Page, i.e. a calendar
1520cb71a62aSAndreas Boehler   *
1521cb71a62aSAndreas Boehler   * @param string $id The page's ID
1522cb71a62aSAndreas Boehler   * @param string $user (optional) The user's ID
1523cb71a62aSAndreas Boehler   *
1524cb71a62aSAndreas Boehler   * @return mixed The sync url or false
1525cb71a62aSAndreas Boehler   */
1526b269830cSAndreas Boehler  public function getSyncUrlForPage($id, $user = null)
1527b269830cSAndreas Boehler  {
1528415f5c8fSscottleechua      if(is_null($user))
152934a47953SAndreas Boehler      {
153034a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
153134a47953SAndreas Boehler        {
1532415f5c8fSscottleechua          $user = $_SERVER['REMOTE_USER'];
153334a47953SAndreas Boehler        }
153434a47953SAndreas Boehler        else
153534a47953SAndreas Boehler        {
153634a47953SAndreas Boehler          return false;
153734a47953SAndreas Boehler        }
153834a47953SAndreas Boehler      }
1539b269830cSAndreas Boehler
1540b269830cSAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1541b269830cSAndreas Boehler      if($calid === false)
1542b269830cSAndreas Boehler        return false;
1543b269830cSAndreas Boehler
1544b269830cSAndreas Boehler      $calsettings = $this->getCalendarSettings($calid);
1545b269830cSAndreas Boehler      if(!isset($calsettings['uri']))
1546b269830cSAndreas Boehler        return false;
1547b269830cSAndreas Boehler
1548b269830cSAndreas Boehler      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
1549b269830cSAndreas Boehler      return $syncurl;
1550b269830cSAndreas Boehler  }
1551b269830cSAndreas Boehler
1552cb71a62aSAndreas Boehler  /**
1553cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given page
1554cb71a62aSAndreas Boehler   *
1555cb71a62aSAndreas Boehler   * @param string $id the page ID
1556cb71a62aSAndreas Boehler   *
1557cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1558cb71a62aSAndreas Boehler   */
1559f69bb449SAndreas Boehler  public function getPrivateURLForPage($id)
1560f69bb449SAndreas Boehler  {
1561a8a4aa52Sscottleechua      // Check if this is an aggregated calendar (has multiple calendar pages)
1562a8a4aa52Sscottleechua      $calendarPages = $this->getCalendarPagesByMeta($id);
1563a8a4aa52Sscottleechua
1564a8a4aa52Sscottleechua      if($calendarPages !== false && count($calendarPages) > 1)
1565a8a4aa52Sscottleechua      {
1566a8a4aa52Sscottleechua          // This is an aggregated calendar - create a special private URL
1567a8a4aa52Sscottleechua          return $this->getPrivateURLForAggregatedCalendar($id, $calendarPages);
1568a8a4aa52Sscottleechua      }
1569a8a4aa52Sscottleechua
1570a8a4aa52Sscottleechua      // Single calendar - use the original logic
1571f69bb449SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1572f69bb449SAndreas Boehler      if($calid === false)
1573f69bb449SAndreas Boehler        return false;
1574f69bb449SAndreas Boehler
1575f69bb449SAndreas Boehler      return $this->getPrivateURLForCalendar($calid);
1576f69bb449SAndreas Boehler  }
1577f69bb449SAndreas Boehler
1578cb71a62aSAndreas Boehler  /**
1579cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given calendar ID
1580cb71a62aSAndreas Boehler   *
1581cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
1582cb71a62aSAndreas Boehler   *
1583cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1584cb71a62aSAndreas Boehler   */
1585f69bb449SAndreas Boehler  public function getPrivateURLForCalendar($calid)
1586f69bb449SAndreas Boehler  {
1587185e2535SAndreas Boehler      if(isset($this->cachedValues['privateurl'][$calid]))
1588185e2535SAndreas Boehler        return $this->cachedValues['privateurl'][$calid];
15895f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
15905f2c3e2dSAndreas Boehler      if(!$sqlite)
15915f2c3e2dSAndreas Boehler        return false;
159251f4febbSAndreas Boehler      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
15935f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
15945f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
1595f69bb449SAndreas Boehler      if(!isset($row['url']))
1596f69bb449SAndreas Boehler      {
159702eea9b8SAndreas Boehler          $url = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics';
159851f4febbSAndreas Boehler          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
15995f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $url, $calid);
1600f69bb449SAndreas Boehler          if($res === false)
1601f69bb449SAndreas Boehler            return false;
1602f69bb449SAndreas Boehler      }
1603f69bb449SAndreas Boehler      else
1604f69bb449SAndreas Boehler      {
1605f69bb449SAndreas Boehler          $url = $row['url'];
1606f69bb449SAndreas Boehler      }
1607185e2535SAndreas Boehler
1608185e2535SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
1609185e2535SAndreas Boehler      $this->cachedValues['privateurl'][$calid] = $url;
1610185e2535SAndreas Boehler      return $url;
1611f69bb449SAndreas Boehler  }
1612f69bb449SAndreas Boehler
1613cb71a62aSAndreas Boehler  /**
1614a8a4aa52Sscottleechua   * Return the private calendar's URL for an aggregated calendar
1615a8a4aa52Sscottleechua   *
1616a8a4aa52Sscottleechua   * @param string $id the page ID
1617a8a4aa52Sscottleechua   * @param array $calendarPages the calendar pages in the aggregation
1618a8a4aa52Sscottleechua   *
1619a8a4aa52Sscottleechua   * @return mixed The private URL or false
1620a8a4aa52Sscottleechua   */
1621a8a4aa52Sscottleechua  public function getPrivateURLForAggregatedCalendar($id, $calendarPages)
1622a8a4aa52Sscottleechua  {
1623a8a4aa52Sscottleechua      // Create a unique identifier for this aggregated calendar
1624a8a4aa52Sscottleechua      $aggregateId = 'aggregated-' . md5($id . serialize($calendarPages));
1625a8a4aa52Sscottleechua
1626a8a4aa52Sscottleechua      if(isset($this->cachedValues['privateurl'][$aggregateId]))
1627a8a4aa52Sscottleechua        return $this->cachedValues['privateurl'][$aggregateId];
1628a8a4aa52Sscottleechua
1629a8a4aa52Sscottleechua      $sqlite = $this->getDB();
1630a8a4aa52Sscottleechua      if(!$sqlite)
1631a8a4aa52Sscottleechua        return false;
1632a8a4aa52Sscottleechua
1633a8a4aa52Sscottleechua      // Check if we already have a private URL for this aggregated calendar
1634a8a4aa52Sscottleechua      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
1635a8a4aa52Sscottleechua      $res = $sqlite->query($query, $aggregateId);
1636a8a4aa52Sscottleechua      $row = $sqlite->res2row($res);
1637a8a4aa52Sscottleechua
1638a8a4aa52Sscottleechua      if(!isset($row['url']))
1639a8a4aa52Sscottleechua      {
1640a8a4aa52Sscottleechua          $url = 'dokuwiki-aggregated-' . bin2hex(random_bytes(16)) . '.ics';
1641a8a4aa52Sscottleechua          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
1642a8a4aa52Sscottleechua          $res = $sqlite->query($query, $url, $aggregateId);
1643a8a4aa52Sscottleechua          if($res === false)
1644a8a4aa52Sscottleechua            return false;
1645a8a4aa52Sscottleechua
1646a8a4aa52Sscottleechua          // Also store the mapping to the page ID for later retrieval
1647a8a4aa52Sscottleechua          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES(?, ?)";
1648a8a4aa52Sscottleechua          $res = $sqlite->query($query, $id, $aggregateId);
1649a8a4aa52Sscottleechua          if($res === false)
1650a8a4aa52Sscottleechua            return false;
1651a8a4aa52Sscottleechua      }
1652a8a4aa52Sscottleechua      else
1653a8a4aa52Sscottleechua      {
1654a8a4aa52Sscottleechua          $url = $row['url'];
1655a8a4aa52Sscottleechua      }
1656a8a4aa52Sscottleechua
1657a8a4aa52Sscottleechua      $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
1658a8a4aa52Sscottleechua      $this->cachedValues['privateurl'][$aggregateId] = $url;
1659a8a4aa52Sscottleechua      return $url;
1660a8a4aa52Sscottleechua  }
1661a8a4aa52Sscottleechua
1662a8a4aa52Sscottleechua  /**
1663cb71a62aSAndreas Boehler   * Retrieve the calendar ID for a given private calendar URL
1664cb71a62aSAndreas Boehler   *
1665cb71a62aSAndreas Boehler   * @param string $url The private URL
1666cb71a62aSAndreas Boehler   *
1667cb71a62aSAndreas Boehler   * @return mixed The calendar ID or false
1668cb71a62aSAndreas Boehler   */
1669f69bb449SAndreas Boehler  public function getCalendarForPrivateURL($url)
1670f69bb449SAndreas Boehler  {
16715f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
16725f2c3e2dSAndreas Boehler      if(!$sqlite)
16735f2c3e2dSAndreas Boehler        return false;
167451f4febbSAndreas Boehler      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
16755f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $url);
16765f2c3e2dSAndreas Boehler      $row = $sqlite->res2row($res);
1677f69bb449SAndreas Boehler      if(!isset($row['calid']))
1678f69bb449SAndreas Boehler        return false;
1679f69bb449SAndreas Boehler      return $row['calid'];
1680f69bb449SAndreas Boehler  }
1681f69bb449SAndreas Boehler
1682cb71a62aSAndreas Boehler  /**
1683cb71a62aSAndreas Boehler   * Return a given calendar as ICS feed, i.e. all events in one ICS file.
1684cb71a62aSAndreas Boehler   *
16857e0b8590SAndreas Boehler   * @param string $calid The calendar ID to retrieve
1686cb71a62aSAndreas Boehler   *
1687cb71a62aSAndreas Boehler   * @return mixed The calendar events as string or false
1688cb71a62aSAndreas Boehler   */
1689f69bb449SAndreas Boehler  public function getCalendarAsICSFeed($calid)
1690f69bb449SAndreas Boehler  {
1691f69bb449SAndreas Boehler      $calSettings = $this->getCalendarSettings($calid);
1692f69bb449SAndreas Boehler      if($calSettings === false)
1693f69bb449SAndreas Boehler        return false;
1694f69bb449SAndreas Boehler      $events = $this->getAllCalendarEvents($calid);
1695f69bb449SAndreas Boehler      if($events === false)
1696f69bb449SAndreas Boehler        return false;
1697f69bb449SAndreas Boehler
16987e0b8590SAndreas Boehler      // Load SabreDAV
16997e0b8590SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
17007e0b8590SAndreas Boehler      $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
17017e0b8590SAndreas Boehler      $out .= $calSettings['displayname']."\r\n";
1702f69bb449SAndreas Boehler      foreach($events as $event)
1703f69bb449SAndreas Boehler      {
17047e0b8590SAndreas Boehler          $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
17057e0b8590SAndreas Boehler          $evt = $vcal->VEVENT;
17067e0b8590SAndreas Boehler          $out .= $evt->serialize();
1707f69bb449SAndreas Boehler      }
17087e0b8590SAndreas Boehler      $out .= "END:VCALENDAR\r\n";
1709f69bb449SAndreas Boehler      return $out;
1710f69bb449SAndreas Boehler  }
1711f69bb449SAndreas Boehler
17127c7c6b0bSAndreas Boehler  /**
1713a8a4aa52Sscottleechua   * Return an aggregated calendar as ICS feed, combining multiple calendars.
1714a8a4aa52Sscottleechua   *
1715a8a4aa52Sscottleechua   * @param string $icsFile The ICS file name for the aggregated calendar
1716a8a4aa52Sscottleechua   *
1717a8a4aa52Sscottleechua   * @return mixed The combined calendar events as string or false
1718a8a4aa52Sscottleechua   */
1719a8a4aa52Sscottleechua  public function getAggregatedCalendarAsICSFeed($icsFile)
1720a8a4aa52Sscottleechua  {
1721a8a4aa52Sscottleechua      $sqlite = $this->getDB();
1722a8a4aa52Sscottleechua      if(!$sqlite)
1723a8a4aa52Sscottleechua        return false;
1724a8a4aa52Sscottleechua
1725a8a4aa52Sscottleechua      // Find the aggregated calendar ID from the URL
1726a8a4aa52Sscottleechua      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
1727a8a4aa52Sscottleechua      $res = $sqlite->query($query, $icsFile);
1728a8a4aa52Sscottleechua      $row = $sqlite->res2row($res);
1729a8a4aa52Sscottleechua
1730a8a4aa52Sscottleechua      if(!isset($row['calid']))
1731a8a4aa52Sscottleechua        return false;
1732a8a4aa52Sscottleechua
1733a8a4aa52Sscottleechua      $aggregateId = $row['calid'];
1734a8a4aa52Sscottleechua
1735a8a4aa52Sscottleechua      // Get the page ID for this aggregated calendar
1736a8a4aa52Sscottleechua      $query = "SELECT page FROM pagetocalendarmapping WHERE calid = ?";
1737a8a4aa52Sscottleechua      $res = $sqlite->query($query, $aggregateId);
1738a8a4aa52Sscottleechua      $row = $sqlite->res2row($res);
1739a8a4aa52Sscottleechua
1740a8a4aa52Sscottleechua      if(!isset($row['page']))
1741a8a4aa52Sscottleechua        return false;
1742a8a4aa52Sscottleechua
1743a8a4aa52Sscottleechua      $pageId = $row['page'];
1744a8a4aa52Sscottleechua
1745a8a4aa52Sscottleechua      // Get the calendar pages for this aggregated calendar
1746a8a4aa52Sscottleechua      $calendarPages = $this->getCalendarPagesByMeta($pageId);
1747a8a4aa52Sscottleechua      if($calendarPages === false || count($calendarPages) <= 1)
1748a8a4aa52Sscottleechua        return false;
1749a8a4aa52Sscottleechua
1750a8a4aa52Sscottleechua      // Load SabreDAV
1751a8a4aa52Sscottleechua      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1752a8a4aa52Sscottleechua
1753a8a4aa52Sscottleechua      // Start building the combined ICS
1754a8a4aa52Sscottleechua      $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
1755a8a4aa52Sscottleechua      $out .= "Aggregated Calendar\r\n";
1756a8a4aa52Sscottleechua
1757a8a4aa52Sscottleechua      // Combine events from all calendars
1758a8a4aa52Sscottleechua      foreach($calendarPages as $calPage => $color)
1759a8a4aa52Sscottleechua      {
1760a8a4aa52Sscottleechua          $calid = $this->getCalendarIdForPage($calPage);
1761a8a4aa52Sscottleechua          if($calid === false)
1762a8a4aa52Sscottleechua            continue;
1763a8a4aa52Sscottleechua
1764a8a4aa52Sscottleechua          $events = $this->getAllCalendarEvents($calid);
1765a8a4aa52Sscottleechua          if($events === false)
1766a8a4aa52Sscottleechua            continue;
1767a8a4aa52Sscottleechua
1768a8a4aa52Sscottleechua          foreach($events as $event)
1769a8a4aa52Sscottleechua          {
1770a8a4aa52Sscottleechua              $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
1771a8a4aa52Sscottleechua              $evt = $vcal->VEVENT;
1772a8a4aa52Sscottleechua              $out .= $evt->serialize();
1773a8a4aa52Sscottleechua          }
1774a8a4aa52Sscottleechua      }
1775a8a4aa52Sscottleechua
1776a8a4aa52Sscottleechua      $out .= "END:VCALENDAR\r\n";
1777a8a4aa52Sscottleechua      return $out;
1778a8a4aa52Sscottleechua  }
1779a8a4aa52Sscottleechua
1780a8a4aa52Sscottleechua  /**
17817c7c6b0bSAndreas Boehler   * Retrieve a configuration option for the plugin
17827c7c6b0bSAndreas Boehler   *
17837c7c6b0bSAndreas Boehler   * @param string $key The key to query
178421d04f73SAndreas Boehler   * @return mixed The option set, null if not found
17857c7c6b0bSAndreas Boehler   */
17867c7c6b0bSAndreas Boehler  public function getConfig($key)
17877c7c6b0bSAndreas Boehler  {
17887c7c6b0bSAndreas Boehler      return $this->getConf($key);
17897c7c6b0bSAndreas Boehler  }
17907c7c6b0bSAndreas Boehler
1791d5703f5aSAndreas Boehler  /**
1792d5703f5aSAndreas Boehler   * Parses some information from calendar objects, used for optimized
1793d5703f5aSAndreas Boehler   * calendar-queries. Taken nearly unmodified from Sabre's PDO backend
1794d5703f5aSAndreas Boehler   *
1795d5703f5aSAndreas Boehler   * Returns an array with the following keys:
1796d5703f5aSAndreas Boehler   *   * etag - An md5 checksum of the object without the quotes.
1797d5703f5aSAndreas Boehler   *   * size - Size of the object in bytes
1798d5703f5aSAndreas Boehler   *   * componentType - VEVENT, VTODO or VJOURNAL
1799d5703f5aSAndreas Boehler   *   * firstOccurence
1800d5703f5aSAndreas Boehler   *   * lastOccurence
1801d5703f5aSAndreas Boehler   *   * uid - value of the UID property
1802d5703f5aSAndreas Boehler   *
1803d5703f5aSAndreas Boehler   * @param string $calendarData
1804d5703f5aSAndreas Boehler   * @return array
1805d5703f5aSAndreas Boehler   */
1806d5703f5aSAndreas Boehler  protected function getDenormalizedData($calendarData)
1807d5703f5aSAndreas Boehler  {
1808d5703f5aSAndreas Boehler    require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1809d5703f5aSAndreas Boehler
1810d5703f5aSAndreas Boehler    $vObject = \Sabre\VObject\Reader::read($calendarData);
1811d5703f5aSAndreas Boehler    $componentType = null;
1812d5703f5aSAndreas Boehler    $component = null;
1813d5703f5aSAndreas Boehler    $firstOccurence = null;
1814d5703f5aSAndreas Boehler    $lastOccurence = null;
1815d5703f5aSAndreas Boehler    $uid = null;
1816d5703f5aSAndreas Boehler    foreach ($vObject->getComponents() as $component)
1817d5703f5aSAndreas Boehler    {
1818d5703f5aSAndreas Boehler        if ($component->name !== 'VTIMEZONE')
1819d5703f5aSAndreas Boehler        {
1820d5703f5aSAndreas Boehler            $componentType = $component->name;
1821d5703f5aSAndreas Boehler            $uid = (string)$component->UID;
1822d5703f5aSAndreas Boehler            break;
1823d5703f5aSAndreas Boehler        }
1824d5703f5aSAndreas Boehler    }
1825d5703f5aSAndreas Boehler    if (!$componentType)
1826d5703f5aSAndreas Boehler    {
1827d5703f5aSAndreas Boehler        return false;
1828d5703f5aSAndreas Boehler    }
1829d5703f5aSAndreas Boehler    if ($componentType === 'VEVENT')
1830d5703f5aSAndreas Boehler    {
1831d5703f5aSAndreas Boehler        $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
1832d5703f5aSAndreas Boehler        // Finding the last occurence is a bit harder
1833d5703f5aSAndreas Boehler        if (!isset($component->RRULE))
1834d5703f5aSAndreas Boehler        {
1835d5703f5aSAndreas Boehler            if (isset($component->DTEND))
1836d5703f5aSAndreas Boehler            {
1837d5703f5aSAndreas Boehler                $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
1838d5703f5aSAndreas Boehler            }
1839d5703f5aSAndreas Boehler            elseif (isset($component->DURATION))
1840d5703f5aSAndreas Boehler            {
1841d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1842d5703f5aSAndreas Boehler                $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->getValue()));
1843d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1844d5703f5aSAndreas Boehler            }
1845d5703f5aSAndreas Boehler            elseif (!$component->DTSTART->hasTime())
1846d5703f5aSAndreas Boehler            {
1847d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1848d5703f5aSAndreas Boehler                $endDate->modify('+1 day');
1849d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1850d5703f5aSAndreas Boehler            }
1851d5703f5aSAndreas Boehler            else
1852d5703f5aSAndreas Boehler            {
1853d5703f5aSAndreas Boehler                $lastOccurence = $firstOccurence;
1854d5703f5aSAndreas Boehler            }
1855d5703f5aSAndreas Boehler        }
1856d5703f5aSAndreas Boehler        else
1857d5703f5aSAndreas Boehler        {
1858d5703f5aSAndreas Boehler            $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID);
1859d5703f5aSAndreas Boehler            $maxDate = new \DateTime('2038-01-01');
1860d5703f5aSAndreas Boehler            if ($it->isInfinite())
1861d5703f5aSAndreas Boehler            {
1862d5703f5aSAndreas Boehler                $lastOccurence = $maxDate->getTimeStamp();
1863d5703f5aSAndreas Boehler            }
1864d5703f5aSAndreas Boehler            else
1865d5703f5aSAndreas Boehler            {
1866d5703f5aSAndreas Boehler                $end = $it->getDtEnd();
1867d5703f5aSAndreas Boehler                while ($it->valid() && $end < $maxDate)
1868d5703f5aSAndreas Boehler                {
1869d5703f5aSAndreas Boehler                    $end = $it->getDtEnd();
1870d5703f5aSAndreas Boehler                    $it->next();
1871d5703f5aSAndreas Boehler                }
1872d5703f5aSAndreas Boehler                $lastOccurence = $end->getTimeStamp();
1873d5703f5aSAndreas Boehler            }
1874d5703f5aSAndreas Boehler        }
1875d5703f5aSAndreas Boehler    }
1876d5703f5aSAndreas Boehler
1877d5703f5aSAndreas Boehler    return array(
1878d5703f5aSAndreas Boehler        'etag'           => md5($calendarData),
1879d5703f5aSAndreas Boehler        'size'           => strlen($calendarData),
1880d5703f5aSAndreas Boehler        'componentType'  => $componentType,
1881d5703f5aSAndreas Boehler        'firstOccurence' => $firstOccurence,
1882d5703f5aSAndreas Boehler        'lastOccurence'  => $lastOccurence,
1883d5703f5aSAndreas Boehler        'uid'            => $uid,
1884d5703f5aSAndreas Boehler    );
1885d5703f5aSAndreas Boehler
1886d5703f5aSAndreas Boehler  }
1887d5703f5aSAndreas Boehler
1888d5703f5aSAndreas Boehler  /**
1889d5703f5aSAndreas Boehler   * Query a calendar by ID and taking several filters into account.
1890d5703f5aSAndreas Boehler   * This is heavily based on Sabre's PDO backend.
1891d5703f5aSAndreas Boehler   *
1892d5703f5aSAndreas Boehler   * @param int $calendarId The calendar's ID
1893d5703f5aSAndreas Boehler   * @param array $filters The filter array to apply
1894d5703f5aSAndreas Boehler   *
1895d5703f5aSAndreas Boehler   * @return mixed The result
1896d5703f5aSAndreas Boehler   */
1897d5703f5aSAndreas Boehler  public function calendarQuery($calendarId, $filters)
1898d5703f5aSAndreas Boehler  {
1899c42afaebSscottleechua    \dokuwiki\Logger::debug('DAVCAL', 'Calendar query executed', __FILE__, __LINE__);
1900d5703f5aSAndreas Boehler    $componentType = null;
1901d5703f5aSAndreas Boehler    $requirePostFilter = true;
1902d5703f5aSAndreas Boehler    $timeRange = null;
19035f2c3e2dSAndreas Boehler    $sqlite = $this->getDB();
19045f2c3e2dSAndreas Boehler    if(!$sqlite)
19055f2c3e2dSAndreas Boehler      return false;
1906d5703f5aSAndreas Boehler
1907d5703f5aSAndreas Boehler    // if no filters were specified, we don't need to filter after a query
1908d5703f5aSAndreas Boehler    if (!$filters['prop-filters'] && !$filters['comp-filters'])
1909d5703f5aSAndreas Boehler    {
1910d5703f5aSAndreas Boehler        $requirePostFilter = false;
1911d5703f5aSAndreas Boehler    }
1912d5703f5aSAndreas Boehler
1913d5703f5aSAndreas Boehler    // Figuring out if there's a component filter
1914d5703f5aSAndreas Boehler    if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined'])
1915d5703f5aSAndreas Boehler    {
1916d5703f5aSAndreas Boehler        $componentType = $filters['comp-filters'][0]['name'];
1917d5703f5aSAndreas Boehler
1918d5703f5aSAndreas Boehler        // Checking if we need post-filters
1919d5703f5aSAndreas Boehler        if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters'])
1920d5703f5aSAndreas Boehler        {
1921d5703f5aSAndreas Boehler            $requirePostFilter = false;
1922d5703f5aSAndreas Boehler        }
1923d5703f5aSAndreas Boehler        // There was a time-range filter
1924d5703f5aSAndreas Boehler        if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range']))
1925d5703f5aSAndreas Boehler        {
1926d5703f5aSAndreas Boehler            $timeRange = $filters['comp-filters'][0]['time-range'];
1927d5703f5aSAndreas Boehler
1928d5703f5aSAndreas Boehler            // If start time OR the end time is not specified, we can do a
1929d5703f5aSAndreas Boehler            // 100% accurate mysql query.
1930d5703f5aSAndreas Boehler            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end']))
1931d5703f5aSAndreas Boehler            {
1932d5703f5aSAndreas Boehler                $requirePostFilter = false;
1933d5703f5aSAndreas Boehler            }
1934d5703f5aSAndreas Boehler        }
1935d5703f5aSAndreas Boehler
1936d5703f5aSAndreas Boehler    }
1937d5703f5aSAndreas Boehler
1938d5703f5aSAndreas Boehler    if ($requirePostFilter)
1939d5703f5aSAndreas Boehler    {
1940d5703f5aSAndreas Boehler        $query = "SELECT uri, calendardata FROM calendarobjects WHERE calendarid = ?";
1941d5703f5aSAndreas Boehler    }
1942d5703f5aSAndreas Boehler    else
1943d5703f5aSAndreas Boehler    {
1944d5703f5aSAndreas Boehler        $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
1945d5703f5aSAndreas Boehler    }
1946d5703f5aSAndreas Boehler
1947d5703f5aSAndreas Boehler    $values = array(
1948d5703f5aSAndreas Boehler        $calendarId
1949d5703f5aSAndreas Boehler    );
1950d5703f5aSAndreas Boehler
1951d5703f5aSAndreas Boehler    if ($componentType)
1952d5703f5aSAndreas Boehler    {
1953d5703f5aSAndreas Boehler        $query .= " AND componenttype = ?";
1954d5703f5aSAndreas Boehler        $values[] = $componentType;
1955d5703f5aSAndreas Boehler    }
1956d5703f5aSAndreas Boehler
1957d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['start'])
1958d5703f5aSAndreas Boehler    {
1959d5703f5aSAndreas Boehler        $query .= " AND lastoccurence > ?";
1960d5703f5aSAndreas Boehler        $values[] = $timeRange['start']->getTimeStamp();
1961d5703f5aSAndreas Boehler    }
1962d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['end'])
1963d5703f5aSAndreas Boehler    {
1964d5703f5aSAndreas Boehler        $query .= " AND firstoccurence < ?";
1965d5703f5aSAndreas Boehler        $values[] = $timeRange['end']->getTimeStamp();
1966d5703f5aSAndreas Boehler    }
1967d5703f5aSAndreas Boehler
19685f2c3e2dSAndreas Boehler    $res = $sqlite->query($query, $values);
19695f2c3e2dSAndreas Boehler    $arr = $sqlite->res2arr($res);
1970d5703f5aSAndreas Boehler
1971d5703f5aSAndreas Boehler    $result = array();
1972d5703f5aSAndreas Boehler    foreach($arr as $row)
1973d5703f5aSAndreas Boehler    {
1974d5703f5aSAndreas Boehler        if ($requirePostFilter)
1975d5703f5aSAndreas Boehler        {
1976d5703f5aSAndreas Boehler            if (!$this->validateFilterForObject($row, $filters))
1977d5703f5aSAndreas Boehler            {
1978d5703f5aSAndreas Boehler                continue;
1979d5703f5aSAndreas Boehler            }
1980d5703f5aSAndreas Boehler        }
1981d5703f5aSAndreas Boehler        $result[] = $row['uri'];
1982d5703f5aSAndreas Boehler
1983d5703f5aSAndreas Boehler    }
1984d5703f5aSAndreas Boehler
1985d5703f5aSAndreas Boehler    return $result;
1986d5703f5aSAndreas Boehler  }
1987d5703f5aSAndreas Boehler
1988d5703f5aSAndreas Boehler  /**
1989d5703f5aSAndreas Boehler   * This method validates if a filter (as passed to calendarQuery) matches
1990d5703f5aSAndreas Boehler   * the given object. Taken from Sabre's PDO backend
1991d5703f5aSAndreas Boehler   *
1992d5703f5aSAndreas Boehler   * @param array $object
1993d5703f5aSAndreas Boehler   * @param array $filters
1994d5703f5aSAndreas Boehler   * @return bool
1995d5703f5aSAndreas Boehler   */
1996d5703f5aSAndreas Boehler  protected function validateFilterForObject($object, $filters)
1997d5703f5aSAndreas Boehler  {
1998d5703f5aSAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1999d5703f5aSAndreas Boehler      // Unfortunately, setting the 'calendardata' here is optional. If
2000d5703f5aSAndreas Boehler      // it was excluded, we actually need another call to get this as
2001d5703f5aSAndreas Boehler      // well.
2002d5703f5aSAndreas Boehler      if (!isset($object['calendardata']))
2003d5703f5aSAndreas Boehler      {
2004d5703f5aSAndreas Boehler          $object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']);
2005d5703f5aSAndreas Boehler      }
2006d5703f5aSAndreas Boehler
2007d5703f5aSAndreas Boehler      $vObject = \Sabre\VObject\Reader::read($object['calendardata']);
2008d5703f5aSAndreas Boehler      $validator = new \Sabre\CalDAV\CalendarQueryValidator();
2009d5703f5aSAndreas Boehler
201039787131SAndreas Boehler      $res = $validator->validate($vObject, $filters);
201139787131SAndreas Boehler      return $res;
2012d5703f5aSAndreas Boehler
2013d5703f5aSAndreas Boehler  }
2014d5703f5aSAndreas Boehler
2015d5703f5aSAndreas Boehler  /**
2016d5703f5aSAndreas Boehler   * Retrieve changes for a given calendar based on the given syncToken.
2017d5703f5aSAndreas Boehler   *
2018d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
2019d5703f5aSAndreas Boehler   * @param int $syncToken The supplied sync token
2020d5703f5aSAndreas Boehler   * @param int $syncLevel The sync level
2021d5703f5aSAndreas Boehler   * @param int $limit The limit of changes
2022d5703f5aSAndreas Boehler   *
2023d5703f5aSAndreas Boehler   * @return array The result
2024d5703f5aSAndreas Boehler   */
2025d5703f5aSAndreas Boehler  public function getChangesForCalendar($calid, $syncToken, $syncLevel, $limit = null)
2026d5703f5aSAndreas Boehler  {
2027d5703f5aSAndreas Boehler      // Current synctoken
2028d5703f5aSAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
2029d5703f5aSAndreas Boehler
2030d5703f5aSAndreas Boehler      if ($currentToken === false) return null;
2031d5703f5aSAndreas Boehler
2032d5703f5aSAndreas Boehler      $result = array(
2033d5703f5aSAndreas Boehler          'syncToken' => $currentToken,
2034d5703f5aSAndreas Boehler          'added'     => array(),
2035d5703f5aSAndreas Boehler          'modified'  => array(),
2036d5703f5aSAndreas Boehler          'deleted'   => array(),
2037d5703f5aSAndreas Boehler      );
20385f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
20395f2c3e2dSAndreas Boehler      if(!$sqlite)
20405f2c3e2dSAndreas Boehler        return false;
2041d5703f5aSAndreas Boehler
2042d5703f5aSAndreas Boehler      if ($syncToken)
2043d5703f5aSAndreas Boehler      {
2044d5703f5aSAndreas Boehler
2045d5703f5aSAndreas Boehler          $query = "SELECT uri, operation FROM calendarchanges WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
2046d5703f5aSAndreas Boehler          if ($limit > 0) $query .= " LIMIT " . (int)$limit;
2047d5703f5aSAndreas Boehler
2048d5703f5aSAndreas Boehler          // Fetching all changes
20495f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $syncToken, $currentToken, $calid);
2050d5703f5aSAndreas Boehler          if($res === false)
2051d5703f5aSAndreas Boehler              return null;
2052d5703f5aSAndreas Boehler
20535f2c3e2dSAndreas Boehler          $arr = $sqlite->res2arr($res);
2054d5703f5aSAndreas Boehler          $changes = array();
2055d5703f5aSAndreas Boehler
2056d5703f5aSAndreas Boehler          // This loop ensures that any duplicates are overwritten, only the
2057d5703f5aSAndreas Boehler          // last change on a node is relevant.
2058d5703f5aSAndreas Boehler          foreach($arr as $row)
2059d5703f5aSAndreas Boehler          {
2060d5703f5aSAndreas Boehler              $changes[$row['uri']] = $row['operation'];
2061d5703f5aSAndreas Boehler          }
2062d5703f5aSAndreas Boehler
2063d5703f5aSAndreas Boehler          foreach ($changes as $uri => $operation)
2064d5703f5aSAndreas Boehler          {
2065d5703f5aSAndreas Boehler              switch ($operation)
2066d5703f5aSAndreas Boehler              {
2067d5703f5aSAndreas Boehler                  case 1 :
2068d5703f5aSAndreas Boehler                      $result['added'][] = $uri;
2069d5703f5aSAndreas Boehler                      break;
2070d5703f5aSAndreas Boehler                  case 2 :
2071d5703f5aSAndreas Boehler                      $result['modified'][] = $uri;
2072d5703f5aSAndreas Boehler                      break;
2073d5703f5aSAndreas Boehler                  case 3 :
2074d5703f5aSAndreas Boehler                      $result['deleted'][] = $uri;
2075d5703f5aSAndreas Boehler                      break;
2076d5703f5aSAndreas Boehler              }
2077d5703f5aSAndreas Boehler
2078d5703f5aSAndreas Boehler          }
2079d5703f5aSAndreas Boehler      }
2080d5703f5aSAndreas Boehler      else
2081d5703f5aSAndreas Boehler      {
2082d5703f5aSAndreas Boehler          // No synctoken supplied, this is the initial sync.
2083d5703f5aSAndreas Boehler          $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
20845f2c3e2dSAndreas Boehler          $res = $sqlite->query($query);
20855f2c3e2dSAndreas Boehler          $arr = $sqlite->res2arr($res);
2086d5703f5aSAndreas Boehler
2087d5703f5aSAndreas Boehler          $result['added'] = $arr;
2088d5703f5aSAndreas Boehler      }
2089d5703f5aSAndreas Boehler      return $result;
2090d5703f5aSAndreas Boehler  }
2091d5703f5aSAndreas Boehler
2092a1a3b679SAndreas Boehler}
2093