xref: /plugin/davcal/helper.php (revision ebcdb60ae6bfb38af357114eb335aae1073e39dc)
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() {
205f2c3e2dSAndreas Boehler  }
215f2c3e2dSAndreas Boehler
225f2c3e2dSAndreas Boehler  /** Establish and initialize the database if not already done
235f2c3e2dSAndreas Boehler   * @return sqlite interface or false
245f2c3e2dSAndreas Boehler   */
255f2c3e2dSAndreas Boehler  private function getDB()
265f2c3e2dSAndreas Boehler  {
275f2c3e2dSAndreas Boehler      if($this->sqlite === null)
285f2c3e2dSAndreas Boehler      {
297e2d2436Sscottleechua        $this->sqlite = new \dokuwiki\plugin\sqlite\SQLiteDB('davcal', DOKU_PLUGIN.'davcal/db/');
30a1a3b679SAndreas Boehler        if(!$this->sqlite)
31a1a3b679SAndreas Boehler        {
32c42afaebSscottleechua            \dokuwiki\Logger::error('DAVCAL', 'This plugin requires the sqlite plugin. Please install it.', __FILE__, __LINE__);
335f2c3e2dSAndreas Boehler            msg('This plugin requires the sqlite plugin. Please install it.', -1);
345f2c3e2dSAndreas Boehler            return false;
35a1a3b679SAndreas Boehler        }
36a1a3b679SAndreas Boehler      }
375f2c3e2dSAndreas Boehler      return $this->sqlite;
385f2c3e2dSAndreas Boehler  }
39a1a3b679SAndreas Boehler
40cb71a62aSAndreas Boehler  /**
41185e2535SAndreas Boehler   * Retrieve meta data for a given page
42185e2535SAndreas Boehler   *
43185e2535SAndreas Boehler   * @param string $id optional The page ID
44185e2535SAndreas Boehler   * @return array The metadata
45185e2535SAndreas Boehler   */
46185e2535SAndreas Boehler  private function getMeta($id = null) {
47185e2535SAndreas Boehler    global $ID;
48185e2535SAndreas Boehler    global $INFO;
49185e2535SAndreas Boehler
50185e2535SAndreas Boehler    if ($id === null) $id = $ID;
51185e2535SAndreas Boehler
52185e2535SAndreas Boehler    if($ID === $id && $INFO['meta']) {
53185e2535SAndreas Boehler        $meta = $INFO['meta'];
54185e2535SAndreas Boehler    } else {
55185e2535SAndreas Boehler        $meta = p_get_metadata($id);
56185e2535SAndreas Boehler    }
57185e2535SAndreas Boehler
58185e2535SAndreas Boehler    return $meta;
59185e2535SAndreas Boehler  }
60185e2535SAndreas Boehler
61185e2535SAndreas Boehler  /**
62185e2535SAndreas Boehler   * Retrieve the meta data for a given page
63185e2535SAndreas Boehler   *
64185e2535SAndreas Boehler   * @param string $id optional The page ID
65185e2535SAndreas Boehler   * @return array with meta data
66185e2535SAndreas Boehler   */
67185e2535SAndreas Boehler  public function getCalendarMetaForPage($id = null)
68185e2535SAndreas Boehler  {
69185e2535SAndreas Boehler      if(is_null($id))
70185e2535SAndreas Boehler      {
71185e2535SAndreas Boehler          global $ID;
72185e2535SAndreas Boehler          $id = $ID;
73185e2535SAndreas Boehler      }
74185e2535SAndreas Boehler
75185e2535SAndreas Boehler      $meta = $this->getMeta($id);
76185e2535SAndreas Boehler      if(isset($meta['plugin_davcal']))
77185e2535SAndreas Boehler        return $meta['plugin_davcal'];
78185e2535SAndreas Boehler      else
79185e2535SAndreas Boehler        return array();
80185e2535SAndreas Boehler  }
81185e2535SAndreas Boehler
82185e2535SAndreas Boehler  /**
83d71c9934SAndreas Boehler   * Check the permission of a user for a given calendar ID
84d71c9934SAndreas Boehler   *
85d71c9934SAndreas Boehler   * @param string $id The calendar ID to check
86d71c9934SAndreas Boehler   * @return int AUTH_* constants
87d71c9934SAndreas Boehler   */
88d71c9934SAndreas Boehler  public function checkCalendarPermission($id)
89d71c9934SAndreas Boehler  {
90d4992453SAndreas Boehler      if(strpos($id, 'webdav://') === 0)
91d71c9934SAndreas Boehler      {
92d71c9934SAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
93d71c9934SAndreas Boehler          if(is_null($wdc))
94d71c9934SAndreas Boehler            return AUTH_NONE;
95d4992453SAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
96d71c9934SAndreas Boehler          $settings = $wdc->getConnection($connectionId);
97d71c9934SAndreas Boehler          if($settings === false)
98d71c9934SAndreas Boehler            return AUTH_NONE;
9958d0e54eSAndreas Boehler          // Check if webdavclient has permissions attached to a page
10058d0e54eSAndreas Boehler          if(!empty($settings['permission']))
10158d0e54eSAndreas Boehler          {
10258d0e54eSAndreas Boehler            $perm = auth_quickaclcheck($settings['permission']);
10358d0e54eSAndreas Boehler            // In case the page has more than read permission, but the
10458d0e54eSAndreas Boehler            // calendar is read-only, we need to modify the permission here.
10558d0e54eSAndreas Boehler            if($perm > AUTH_READ && $settings['write'] == 0)
10658d0e54eSAndreas Boehler              $perm = AUTH_READ;
10758d0e54eSAndreas Boehler            return $perm;
10858d0e54eSAndreas Boehler          }
109d71c9934SAndreas Boehler          if($settings['write'] === '1')
110d71c9934SAndreas Boehler            return AUTH_CREATE;
111d71c9934SAndreas Boehler          return AUTH_READ;
112d71c9934SAndreas Boehler      }
113d71c9934SAndreas Boehler      else
114d71c9934SAndreas Boehler      {
115d71c9934SAndreas Boehler          $calid = $this->getCalendarIdForPage($id);
116d71c9934SAndreas Boehler          // We return AUTH_READ if the calendar does not exist. This makes
117d71c9934SAndreas Boehler          // davcal happy when there are just included calendars
118d71c9934SAndreas Boehler          if($calid === false)
119d71c9934SAndreas Boehler            return AUTH_READ;
120d71c9934SAndreas Boehler          return auth_quickaclcheck($id);
121d71c9934SAndreas Boehler      }
122d71c9934SAndreas Boehler  }
123d71c9934SAndreas Boehler
124d71c9934SAndreas Boehler  /**
12580e1ddf7SAndreas Boehler   * Filter calendar pages and return only those where the current
12680e1ddf7SAndreas Boehler   * user has at least read permission.
12780e1ddf7SAndreas Boehler   *
12880e1ddf7SAndreas Boehler   * @param array $calendarPages Array with calendar pages to check
12980e1ddf7SAndreas Boehler   * @return array with filtered calendar pages
13080e1ddf7SAndreas Boehler   */
13180e1ddf7SAndreas Boehler  public function filterCalendarPagesByUserPermission($calendarPages)
13280e1ddf7SAndreas Boehler  {
13380e1ddf7SAndreas Boehler      $retList = array();
13480e1ddf7SAndreas Boehler      foreach($calendarPages as $page => $data)
13580e1ddf7SAndreas Boehler      {
13658d0e54eSAndreas Boehler          if($this->checkCalendarPermission($page) >= AUTH_READ)
13780e1ddf7SAndreas Boehler          {
13880e1ddf7SAndreas Boehler              $retList[$page] = $data;
13980e1ddf7SAndreas Boehler          }
14080e1ddf7SAndreas Boehler      }
14180e1ddf7SAndreas Boehler      return $retList;
14280e1ddf7SAndreas Boehler  }
14380e1ddf7SAndreas Boehler
14480e1ddf7SAndreas Boehler  /**
145185e2535SAndreas Boehler   * Get all calendar pages used by a given page
146185e2535SAndreas Boehler   * based on the stored metadata
147185e2535SAndreas Boehler   *
148185e2535SAndreas Boehler   * @param string $id optional The page id
149*ebcdb60aSscottleechua   * @param bool $skipPermissionCheck optional Skip permission filtering (for private URLs)
150185e2535SAndreas Boehler   * @return mixed The pages as array or false
151185e2535SAndreas Boehler   */
152*ebcdb60aSscottleechua  public function getCalendarPagesByMeta($id = null, $skipPermissionCheck = false)
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      {
164*ebcdb60aSscottleechua          if($skipPermissionCheck)
165*ebcdb60aSscottleechua          {
166*ebcdb60aSscottleechua              // For private URLs, return all pages without permission filtering
167*ebcdb60aSscottleechua              return $meta['id'];
168*ebcdb60aSscottleechua          }
169*ebcdb60aSscottleechua          else
170*ebcdb60aSscottleechua          {
171ed764890SAndreas Boehler              // Filter the list of pages by permission
17280e1ddf7SAndreas Boehler              $pages = $this->filterCalendarPagesByUserPermission($meta['id']);
17380e1ddf7SAndreas Boehler              if(empty($pages))
174ed764890SAndreas Boehler                return false;
17580e1ddf7SAndreas Boehler              return $pages;
176ed764890SAndreas Boehler          }
177*ebcdb60aSscottleechua      }
178185e2535SAndreas Boehler      return false;
179185e2535SAndreas Boehler  }
180185e2535SAndreas Boehler
181185e2535SAndreas Boehler  /**
182185e2535SAndreas Boehler   * Get a list of calendar names/pages/ids/colors
183185e2535SAndreas Boehler   * for an array of page ids
184185e2535SAndreas Boehler   *
185185e2535SAndreas Boehler   * @param array $calendarPages The calendar pages to retrieve
186185e2535SAndreas Boehler   * @return array The list
187185e2535SAndreas Boehler   */
188185e2535SAndreas Boehler  public function getCalendarMapForIDs($calendarPages)
189185e2535SAndreas Boehler  {
190185e2535SAndreas Boehler      $data = array();
1914a2bf5eeSAndreas Boehler      foreach($calendarPages as $page => $color)
192185e2535SAndreas Boehler      {
1930b805092SAndreas Boehler            if(strpos($page, 'webdav://') === 0)
1940b805092SAndreas Boehler            {
1950b805092SAndreas Boehler                $wdc =& plugin_load('helper', 'webdavclient');
1960b805092SAndreas Boehler                if(is_null($wdc))
1970b805092SAndreas Boehler                    continue;
1980b805092SAndreas Boehler                $connectionId = str_replace('webdav://', '', $page);
1990b805092SAndreas Boehler                $settings = $wdc->getConnection($connectionId);
2002393a702SAndreas Boehler                if($settings === false)
2012393a702SAndreas Boehler                    continue;
2020b805092SAndreas Boehler                $name = $settings['displayname'];
203d71c9934SAndreas Boehler                $write = ($settings['write'] === '1');
2040b805092SAndreas Boehler                $calid = $connectionId;
205cd2f100dSAndreas Boehler                $color = '#3a87ad';
2060b805092SAndreas Boehler            }
2070b805092SAndreas Boehler            else
2080b805092SAndreas Boehler            {
209185e2535SAndreas Boehler                $calid = $this->getCalendarIdForPage($page);
210185e2535SAndreas Boehler                if($calid !== false)
211185e2535SAndreas Boehler                {
212185e2535SAndreas Boehler                    $settings = $this->getCalendarSettings($calid);
213185e2535SAndreas Boehler                    $name = $settings['displayname'];
214cd2f100dSAndreas Boehler                    $color = $settings['calendarcolor'];
215ed764890SAndreas Boehler                    $write = (auth_quickaclcheck($page) > AUTH_READ);
2160b805092SAndreas Boehler                }
2170b805092SAndreas Boehler                else
2180b805092SAndreas Boehler                {
2190b805092SAndreas Boehler                    continue;
2200b805092SAndreas Boehler                }
2210b805092SAndreas Boehler            }
222185e2535SAndreas Boehler            $data[] = array('name' => $name, 'page' => $page, 'calid' => $calid,
223ed764890SAndreas Boehler                            'color' => $color, 'write' => $write);
224185e2535SAndreas Boehler      }
225185e2535SAndreas Boehler      return $data;
226185e2535SAndreas Boehler  }
227185e2535SAndreas Boehler
228185e2535SAndreas Boehler  /**
229185e2535SAndreas Boehler   * Get the saved calendar color for a given page.
230185e2535SAndreas Boehler   *
231185e2535SAndreas Boehler   * @param string $id optional The page ID
232185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
233185e2535SAndreas Boehler   */
234185e2535SAndreas Boehler  public function getCalendarColorForPage($id = null)
235185e2535SAndreas Boehler  {
236185e2535SAndreas Boehler      if(is_null($id))
237185e2535SAndreas Boehler      {
238185e2535SAndreas Boehler          global $ID;
239185e2535SAndreas Boehler          $id = $ID;
240185e2535SAndreas Boehler      }
241185e2535SAndreas Boehler
242185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
243185e2535SAndreas Boehler      if($calid === false)
244185e2535SAndreas Boehler        return false;
245185e2535SAndreas Boehler
246185e2535SAndreas Boehler      return $this->getCalendarColorForCalendar($calid);
247185e2535SAndreas Boehler  }
248185e2535SAndreas Boehler
249185e2535SAndreas Boehler  /**
250185e2535SAndreas Boehler   * Get the saved calendar color for a given calendar ID.
251185e2535SAndreas Boehler   *
252185e2535SAndreas Boehler   * @param string $id optional The calendar ID
253185e2535SAndreas Boehler   * @return mixed The color on success, otherwise false
254185e2535SAndreas Boehler   */
255185e2535SAndreas Boehler  public function getCalendarColorForCalendar($calid)
256185e2535SAndreas Boehler  {
257185e2535SAndreas Boehler      if(isset($this->cachedValues['calendarcolor'][$calid]))
258185e2535SAndreas Boehler        return $this->cachedValues['calendarcolor'][$calid];
259185e2535SAndreas Boehler
260185e2535SAndreas Boehler      $row = $this->getCalendarSettings($calid);
261185e2535SAndreas Boehler
262185e2535SAndreas Boehler      if(!isset($row['calendarcolor']))
263185e2535SAndreas Boehler        return false;
264185e2535SAndreas Boehler
265185e2535SAndreas Boehler      $color = $row['calendarcolor'];
266185e2535SAndreas Boehler      $this->cachedValues['calendarcolor'][$calid] = $color;
267185e2535SAndreas Boehler      return $color;
268185e2535SAndreas Boehler  }
269185e2535SAndreas Boehler
270185e2535SAndreas Boehler  /**
271e86c8dd3SAndreas Boehler   * Get the user's principal URL for iOS sync
272e86c8dd3SAndreas Boehler   * @param string $user the user name
273e86c8dd3SAndreas Boehler   * @return the URL to the principal sync
274e86c8dd3SAndreas Boehler   */
275e86c8dd3SAndreas Boehler  public function getPrincipalUrlForUser($user)
276e86c8dd3SAndreas Boehler  {
277e86c8dd3SAndreas Boehler      if(is_null($user))
278e86c8dd3SAndreas Boehler        return false;
279e86c8dd3SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/calendarserver.php/principals/'.$user;
280e86c8dd3SAndreas Boehler      return $url;
281e86c8dd3SAndreas Boehler  }
282e86c8dd3SAndreas Boehler
283e86c8dd3SAndreas Boehler  /**
284185e2535SAndreas Boehler   * Set the calendar color for a given page.
285185e2535SAndreas Boehler   *
286185e2535SAndreas Boehler   * @param string $color The color definition
287185e2535SAndreas Boehler   * @param string $id optional The page ID
288185e2535SAndreas Boehler   * @return boolean True on success, otherwise false
289185e2535SAndreas Boehler   */
290185e2535SAndreas Boehler  public function setCalendarColorForPage($color, $id = null)
291185e2535SAndreas Boehler  {
292185e2535SAndreas Boehler      if(is_null($id))
293185e2535SAndreas Boehler      {
294185e2535SAndreas Boehler          global $ID;
295185e2535SAndreas Boehler          $id = $ID;
296185e2535SAndreas Boehler      }
297185e2535SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
298185e2535SAndreas Boehler      if($calid === false)
299185e2535SAndreas Boehler        return false;
300185e2535SAndreas Boehler
3015f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3025f2c3e2dSAndreas Boehler      if(!$sqlite)
3035f2c3e2dSAndreas Boehler        return false;
30451f4febbSAndreas Boehler      $query = "UPDATE calendars SET calendarcolor = ? ".
30551f4febbSAndreas Boehler               " WHERE id = ?";
3065f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $color, $calid);
307185e2535SAndreas Boehler      if($res !== false)
308185e2535SAndreas Boehler      {
309185e2535SAndreas Boehler        $this->cachedValues['calendarcolor'][$calid] = $color;
310185e2535SAndreas Boehler        return true;
311185e2535SAndreas Boehler      }
312185e2535SAndreas Boehler      return false;
313185e2535SAndreas Boehler  }
314185e2535SAndreas Boehler
315185e2535SAndreas Boehler  /**
316cb71a62aSAndreas Boehler   * Set the calendar name and description for a given page with a given
317cb71a62aSAndreas Boehler   * page id.
318cb71a62aSAndreas Boehler   * If the calendar doesn't exist, the calendar is created!
319cb71a62aSAndreas Boehler   *
320cb71a62aSAndreas Boehler   * @param string  $name The name of the new calendar
321cb71a62aSAndreas Boehler   * @param string  $description The description of the new calendar
322cb71a62aSAndreas Boehler   * @param string  $id (optional) The ID of the page
323cb71a62aSAndreas Boehler   * @param string  $userid The userid of the creating user
324cb71a62aSAndreas Boehler   *
325cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false.
326cb71a62aSAndreas Boehler   */
327a1a3b679SAndreas Boehler  public function setCalendarNameForPage($name, $description, $id = null, $userid = null)
328a1a3b679SAndreas Boehler  {
329a1a3b679SAndreas Boehler      if(is_null($id))
330a1a3b679SAndreas Boehler      {
331a1a3b679SAndreas Boehler          global $ID;
332a1a3b679SAndreas Boehler          $id = $ID;
333a1a3b679SAndreas Boehler      }
334a1a3b679SAndreas Boehler      if(is_null($userid))
33534a47953SAndreas Boehler      {
33634a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
33734a47953SAndreas Boehler        {
338a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
33934a47953SAndreas Boehler        }
34034a47953SAndreas Boehler        else
34134a47953SAndreas Boehler        {
34234a47953SAndreas Boehler          $userid = uniqid('davcal-');
34334a47953SAndreas Boehler        }
34434a47953SAndreas Boehler      }
345a1a3b679SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
346a1a3b679SAndreas Boehler      if($calid === false)
347a1a3b679SAndreas Boehler        return $this->createCalendarForPage($name, $description, $id, $userid);
348a1a3b679SAndreas Boehler
3495f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3505f2c3e2dSAndreas Boehler      if(!$sqlite)
3515f2c3e2dSAndreas Boehler        return false;
35251f4febbSAndreas Boehler      $query = "UPDATE calendars SET displayname = ?, description = ? WHERE id = ?";
3535f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $name, $description, $calid);
354b269830cSAndreas Boehler      if($res !== false)
355b269830cSAndreas Boehler        return true;
356b269830cSAndreas Boehler      return false;
357a1a3b679SAndreas Boehler  }
358a1a3b679SAndreas Boehler
359cb71a62aSAndreas Boehler  /**
360d5703f5aSAndreas Boehler   * Update a calendar's displayname
361d5703f5aSAndreas Boehler   *
362d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
363d5703f5aSAndreas Boehler   * @param string $name The new calendar name
364d5703f5aSAndreas Boehler   *
365d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
366d5703f5aSAndreas Boehler   */
367d5703f5aSAndreas Boehler  public function updateCalendarName($calid, $name)
368d5703f5aSAndreas Boehler  {
3695f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3705f2c3e2dSAndreas Boehler      if(!$sqlite)
3715f2c3e2dSAndreas Boehler        return false;
372d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET displayname = ? WHERE id = ?";
3735f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $name);
374d5703f5aSAndreas Boehler      if($res !== false)
375d5703f5aSAndreas Boehler      {
376d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
377d5703f5aSAndreas Boehler        return true;
378d5703f5aSAndreas Boehler      }
379d5703f5aSAndreas Boehler      return false;
380d5703f5aSAndreas Boehler  }
381d5703f5aSAndreas Boehler
382d5703f5aSAndreas Boehler  /**
383d5703f5aSAndreas Boehler   * Update the calendar description
384d5703f5aSAndreas Boehler   *
385d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
386d5703f5aSAndreas Boehler   * @param string $description The new calendar's description
387d5703f5aSAndreas Boehler   *
388d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
389d5703f5aSAndreas Boehler   */
390d5703f5aSAndreas Boehler  public function updateCalendarDescription($calid, $description)
391d5703f5aSAndreas Boehler  {
3925f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
3935f2c3e2dSAndreas Boehler      if(!$sqlite)
3945f2c3e2dSAndreas Boehler        return false;
395d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET description = ? WHERE id = ?";
3965f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $description);
397d5703f5aSAndreas Boehler      if($res !== false)
398d5703f5aSAndreas Boehler      {
399d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
400d5703f5aSAndreas Boehler        return true;
401d5703f5aSAndreas Boehler      }
402d5703f5aSAndreas Boehler      return false;
403d5703f5aSAndreas Boehler  }
404d5703f5aSAndreas Boehler
405d5703f5aSAndreas Boehler  /**
406d5703f5aSAndreas Boehler   * Update a calendar's timezone information
407d5703f5aSAndreas Boehler   *
408d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
409d5703f5aSAndreas Boehler   * @param string $timezone The new timezone to set
410d5703f5aSAndreas Boehler   *
411d5703f5aSAndreas Boehler   * @return boolean True on success, otherwise false
412d5703f5aSAndreas Boehler   */
413d5703f5aSAndreas Boehler  public function updateCalendarTimezone($calid, $timezone)
414d5703f5aSAndreas Boehler  {
4155f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
4165f2c3e2dSAndreas Boehler      if(!$sqlite)
4175f2c3e2dSAndreas Boehler        return false;
418d5703f5aSAndreas Boehler      $query = "UPDATE calendars SET timezone = ? WHERE id = ?";
4195f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $timezone);
420d5703f5aSAndreas Boehler      if($res !== false)
421d5703f5aSAndreas Boehler      {
422d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, '', 'modified');
423d5703f5aSAndreas Boehler        return true;
424d5703f5aSAndreas Boehler      }
425d5703f5aSAndreas Boehler      return false;
426d5703f5aSAndreas Boehler  }
427d5703f5aSAndreas Boehler
428d5703f5aSAndreas Boehler  /**
429cb71a62aSAndreas Boehler   * Save the personal settings to the SQLite database 'calendarsettings'.
430cb71a62aSAndreas Boehler   *
431cb71a62aSAndreas Boehler   * @param array  $settings The settings array to store
432cb71a62aSAndreas Boehler   * @param string $userid (optional) The userid to store
433cb71a62aSAndreas Boehler   *
434cb71a62aSAndreas Boehler   * @param boolean True on success, otherwise false
435cb71a62aSAndreas Boehler   */
436a495d34cSAndreas Boehler  public function savePersonalSettings($settings, $userid = null)
437a495d34cSAndreas Boehler  {
438a495d34cSAndreas Boehler      if(is_null($userid))
43934a47953SAndreas Boehler      {
44034a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
44134a47953SAndreas Boehler          {
442a495d34cSAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
44334a47953SAndreas Boehler          }
44434a47953SAndreas Boehler          else
44534a47953SAndreas Boehler          {
44634a47953SAndreas Boehler              return false;
44734a47953SAndreas Boehler          }
44834a47953SAndreas Boehler      }
4495f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
4505f2c3e2dSAndreas Boehler      if(!$sqlite)
4515f2c3e2dSAndreas Boehler        return false;
4525f2c3e2dSAndreas Boehler      $sqlite->query("BEGIN TRANSACTION");
453a495d34cSAndreas Boehler
45451f4febbSAndreas Boehler      $query = "DELETE FROM calendarsettings WHERE userid = ?";
4555f2c3e2dSAndreas Boehler      $sqlite->query($query, $userid);
456bd883736SAndreas Boehler
457a495d34cSAndreas Boehler      foreach($settings as $key => $value)
458a495d34cSAndreas Boehler      {
45951f4febbSAndreas Boehler          $query = "INSERT INTO calendarsettings (userid, key, value) VALUES (?, ?, ?)";
4605f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $userid, $key, $value);
461a495d34cSAndreas Boehler          if($res === false)
462a495d34cSAndreas Boehler              return false;
463a495d34cSAndreas Boehler      }
4645f2c3e2dSAndreas Boehler      $sqlite->query("COMMIT TRANSACTION");
465185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
466a495d34cSAndreas Boehler      return true;
467a495d34cSAndreas Boehler  }
468a495d34cSAndreas Boehler
469cb71a62aSAndreas Boehler  /**
470cb71a62aSAndreas Boehler   * Retrieve the settings array for a given user id.
471cb71a62aSAndreas Boehler   * Some sane defaults are returned, currently:
472cb71a62aSAndreas Boehler   *
473cb71a62aSAndreas Boehler   *    timezone    => local
474cb71a62aSAndreas Boehler   *    weeknumbers => 0
475cb71a62aSAndreas Boehler   *    workweek    => 0
476cb71a62aSAndreas Boehler   *
477cb71a62aSAndreas Boehler   * @param string $userid (optional) The user id to retrieve
478cb71a62aSAndreas Boehler   *
479cb71a62aSAndreas Boehler   * @return array The settings array
480cb71a62aSAndreas Boehler   */
481a495d34cSAndreas Boehler  public function getPersonalSettings($userid = null)
482a495d34cSAndreas Boehler  {
483bd883736SAndreas Boehler      $settings = array(
484fb813b30SAndreas Boehler        'timezone' => $this->getConf('timezone'),
485fb813b30SAndreas Boehler        'weeknumbers' => $this->getConf('weeknumbers'),
486fb813b30SAndreas Boehler        'workweek' => $this->getConf('workweek'),
4871d5bdcd0SAndreas Boehler        'monday' => $this->getConf('monday'),
4881d5bdcd0SAndreas Boehler        'timeformat' => $this->getConf('timeformat')
489bd883736SAndreas Boehler      );
49034a47953SAndreas Boehler      if(is_null($userid))
49134a47953SAndreas Boehler      {
49234a47953SAndreas Boehler          if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
49334a47953SAndreas Boehler          {
49434a47953SAndreas Boehler            $userid = $_SERVER['REMOTE_USER'];
49534a47953SAndreas Boehler          }
49634a47953SAndreas Boehler          else
49734a47953SAndreas Boehler          {
49834a47953SAndreas Boehler            return $settings;
49934a47953SAndreas Boehler          }
50034a47953SAndreas Boehler      }
50134a47953SAndreas Boehler
5025f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
5035f2c3e2dSAndreas Boehler      if(!$sqlite)
5045f2c3e2dSAndreas Boehler        return false;
50534a47953SAndreas Boehler      if(isset($this->cachedValues['settings'][$userid]))
50634a47953SAndreas Boehler        return $this->cachedValues['settings'][$userid];
50751f4febbSAndreas Boehler      $query = "SELECT key, value FROM calendarsettings WHERE userid = ?";
50861fec75eSscottleechua      $arr = $sqlite->queryAll($query, [$userid]);
509a495d34cSAndreas Boehler      foreach($arr as $row)
510a495d34cSAndreas Boehler      {
511a495d34cSAndreas Boehler          $settings[$row['key']] = $row['value'];
512a495d34cSAndreas Boehler      }
513185e2535SAndreas Boehler      $this->cachedValues['settings'][$userid] = $settings;
514a495d34cSAndreas Boehler      return $settings;
515a495d34cSAndreas Boehler  }
516a495d34cSAndreas Boehler
517cb71a62aSAndreas Boehler  /**
518cb71a62aSAndreas Boehler   * Retrieve the calendar ID based on a page ID from the SQLite table
519cb71a62aSAndreas Boehler   * 'pagetocalendarmapping'.
520cb71a62aSAndreas Boehler   *
521cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to retrieve the corresponding calendar
522cb71a62aSAndreas Boehler   *
523cb71a62aSAndreas Boehler   * @return mixed the ID on success, otherwise false
524cb71a62aSAndreas Boehler   */
525a1a3b679SAndreas Boehler  public function getCalendarIdForPage($id = null)
526a1a3b679SAndreas Boehler  {
527a1a3b679SAndreas Boehler      if(is_null($id))
528a1a3b679SAndreas Boehler      {
529a1a3b679SAndreas Boehler          global $ID;
530a1a3b679SAndreas Boehler          $id = $ID;
531a1a3b679SAndreas Boehler      }
532a1a3b679SAndreas Boehler
533185e2535SAndreas Boehler      if(isset($this->cachedValues['calid'][$id]))
534185e2535SAndreas Boehler        return $this->cachedValues['calid'][$id];
535185e2535SAndreas Boehler
5365f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
5375f2c3e2dSAndreas Boehler      if(!$sqlite)
5385f2c3e2dSAndreas Boehler        return false;
53951f4febbSAndreas Boehler      $query = "SELECT calid FROM pagetocalendarmapping WHERE page = ?";
54061fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$id]);
541a1a3b679SAndreas Boehler      if(isset($row['calid']))
542185e2535SAndreas Boehler      {
543185e2535SAndreas Boehler        $calid = $row['calid'];
544185e2535SAndreas Boehler        $this->cachedValues['calid'] = $calid;
545185e2535SAndreas Boehler        return $calid;
546185e2535SAndreas Boehler      }
547a1a3b679SAndreas Boehler      return false;
548a1a3b679SAndreas Boehler  }
549a1a3b679SAndreas Boehler
550cb71a62aSAndreas Boehler  /**
551cb71a62aSAndreas Boehler   * Retrieve the complete calendar id to page mapping.
552cb71a62aSAndreas Boehler   * This is necessary to be able to retrieve a list of
553cb71a62aSAndreas Boehler   * calendars for a given user and check the access rights.
554cb71a62aSAndreas Boehler   *
555cb71a62aSAndreas Boehler   * @return array The mapping array
556cb71a62aSAndreas Boehler   */
557a1a3b679SAndreas Boehler  public function getCalendarIdToPageMapping()
558a1a3b679SAndreas Boehler  {
5595f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
5605f2c3e2dSAndreas Boehler      if(!$sqlite)
5615f2c3e2dSAndreas Boehler        return false;
562a1a3b679SAndreas Boehler      $query = "SELECT calid, page FROM pagetocalendarmapping";
56361fec75eSscottleechua      $arr = $sqlite->queryAll($query, []);
564a1a3b679SAndreas Boehler      return $arr;
565a1a3b679SAndreas Boehler  }
566a1a3b679SAndreas Boehler
567cb71a62aSAndreas Boehler  /**
568cb71a62aSAndreas Boehler   * Retrieve all calendar IDs a given user has access to.
569cb71a62aSAndreas Boehler   * The user is specified by the principalUri, so the
570cb71a62aSAndreas Boehler   * user name is actually split from the URI component.
571cb71a62aSAndreas Boehler   *
572cb71a62aSAndreas Boehler   * Access rights are checked against DokuWiki's ACL
573cb71a62aSAndreas Boehler   * and applied accordingly.
574cb71a62aSAndreas Boehler   *
575cb71a62aSAndreas Boehler   * @param string $principalUri The principal URI to work on
576cb71a62aSAndreas Boehler   *
577cb71a62aSAndreas Boehler   * @return array An associative array of calendar IDs
578cb71a62aSAndreas Boehler   */
579a1a3b679SAndreas Boehler  public function getCalendarIdsForUser($principalUri)
580a1a3b679SAndreas Boehler  {
58134a47953SAndreas Boehler      global $auth;
582a1a3b679SAndreas Boehler      $user = explode('/', $principalUri);
583a1a3b679SAndreas Boehler      $user = end($user);
584a1a3b679SAndreas Boehler      $mapping = $this->getCalendarIdToPageMapping();
585a1a3b679SAndreas Boehler      $calids = array();
58634a47953SAndreas Boehler      $ud = $auth->getUserData($user);
58734a47953SAndreas Boehler      $groups = $ud['grps'];
588a1a3b679SAndreas Boehler      foreach($mapping as $row)
589a1a3b679SAndreas Boehler      {
590a1a3b679SAndreas Boehler          $id = $row['calid'];
59113b16484SAndreas Boehler          $enabled = $this->getCalendarStatus($id);
59213b16484SAndreas Boehler          if($enabled == false)
59313b16484SAndreas Boehler            continue;
594a1a3b679SAndreas Boehler          $page = $row['page'];
59534a47953SAndreas Boehler          $acl = auth_aclcheck($page, $user, $groups);
596a1a3b679SAndreas Boehler          if($acl >= AUTH_READ)
597a1a3b679SAndreas Boehler          {
598a1a3b679SAndreas Boehler              $write = $acl > AUTH_READ;
599a1a3b679SAndreas Boehler              $calids[$id] = array('readonly' => !$write);
600a1a3b679SAndreas Boehler          }
601a1a3b679SAndreas Boehler      }
602a1a3b679SAndreas Boehler      return $calids;
603a1a3b679SAndreas Boehler  }
604a1a3b679SAndreas Boehler
605cb71a62aSAndreas Boehler  /**
606cb71a62aSAndreas Boehler   * Create a new calendar for a given page ID and set name and description
607cb71a62aSAndreas Boehler   * accordingly. Also update the pagetocalendarmapping table on success.
608cb71a62aSAndreas Boehler   *
609cb71a62aSAndreas Boehler   * @param string $name The calendar's name
610cb71a62aSAndreas Boehler   * @param string $description The calendar's description
611cb71a62aSAndreas Boehler   * @param string $id (optional) The page ID to work on
612cb71a62aSAndreas Boehler   * @param string $userid (optional) The user ID that created the calendar
613cb71a62aSAndreas Boehler   *
614cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
615cb71a62aSAndreas Boehler   */
616a1a3b679SAndreas Boehler  public function createCalendarForPage($name, $description, $id = null, $userid = null)
617a1a3b679SAndreas Boehler  {
618a1a3b679SAndreas Boehler      if(is_null($id))
619a1a3b679SAndreas Boehler      {
620a1a3b679SAndreas Boehler          global $ID;
621a1a3b679SAndreas Boehler          $id = $ID;
622a1a3b679SAndreas Boehler      }
623a1a3b679SAndreas Boehler      if(is_null($userid))
62434a47953SAndreas Boehler      {
62534a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
62634a47953SAndreas Boehler        {
627a1a3b679SAndreas Boehler          $userid = $_SERVER['REMOTE_USER'];
62834a47953SAndreas Boehler        }
62934a47953SAndreas Boehler        else
63034a47953SAndreas Boehler        {
63134a47953SAndreas Boehler          $userid = uniqid('davcal-');
63234a47953SAndreas Boehler        }
63334a47953SAndreas Boehler      }
634a1a3b679SAndreas Boehler      $values = array('principals/'.$userid,
635a1a3b679SAndreas Boehler                      $name,
636a1a3b679SAndreas Boehler                      str_replace(array('/', ' ', ':'), '_', $id),
637a1a3b679SAndreas Boehler                      $description,
638a1a3b679SAndreas Boehler                      'VEVENT,VTODO',
63955a741c0SAndreas Boehler                      0,
64055a741c0SAndreas Boehler                      1);
6415f2c3e2dSAndreas Boehler
6425f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
6435f2c3e2dSAndreas Boehler      if(!$sqlite)
6445f2c3e2dSAndreas Boehler        return false;
64551f4febbSAndreas Boehler      $query = "INSERT INTO calendars (principaluri, displayname, uri, description, components, transparent, synctoken) ".
64651f4febbSAndreas Boehler               "VALUES (?, ?, ?, ?, ?, ?, ?)";
64761fec75eSscottleechua      $sqlite->query($query, $values);
648cb71a62aSAndreas Boehler
64951f4febbSAndreas Boehler      $query = "SELECT id FROM calendars WHERE principaluri = ? AND displayname = ? AND ".
65051f4febbSAndreas Boehler               "uri = ? AND description = ?";
65161fec75eSscottleechua      $row = $sqlite->queryRecord($query, array($values[0], $values[1], $values[2], $values[3]));
652cb71a62aSAndreas Boehler
653a1a3b679SAndreas Boehler      if(isset($row['id']))
654a1a3b679SAndreas Boehler      {
65551f4febbSAndreas Boehler          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES (?, ?)";
65661fec75eSscottleechua          $sqlite->query($query, array($id, $row['id']));
65761fec75eSscottleechua          return true;
658a1a3b679SAndreas Boehler      }
659a1a3b679SAndreas Boehler
660a1a3b679SAndreas Boehler      return false;
661a1a3b679SAndreas Boehler  }
662a1a3b679SAndreas Boehler
663cb71a62aSAndreas Boehler  /**
664d5703f5aSAndreas Boehler   * Add a new calendar entry to the given calendar. Calendar data is
665d5703f5aSAndreas Boehler   * specified as ICS file, thus it needs to be parsed first.
666d5703f5aSAndreas Boehler   *
667d5703f5aSAndreas Boehler   * This is mainly needed for the sync support.
668d5703f5aSAndreas Boehler   *
669d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
670d5703f5aSAndreas Boehler   * @param string $uri The new object URI
671d5703f5aSAndreas Boehler   * @param string $ics The ICS file
672d5703f5aSAndreas Boehler   *
673d5703f5aSAndreas Boehler   * @return mixed The etag.
674d5703f5aSAndreas Boehler   */
675d5703f5aSAndreas Boehler  public function addCalendarEntryToCalendarByICS($calid, $uri, $ics)
676d5703f5aSAndreas Boehler  {
677d5703f5aSAndreas Boehler    $extraData = $this->getDenormalizedData($ics);
678d5703f5aSAndreas Boehler
6795f2c3e2dSAndreas Boehler    $sqlite = $this->getDB();
6805f2c3e2dSAndreas Boehler    if(!$sqlite)
6815f2c3e2dSAndreas Boehler      return false;
682d5703f5aSAndreas Boehler    $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, etag, size, componenttype, firstoccurence, lastoccurence, uid) VALUES (?,?,?,?,?,?,?,?,?,?)";
6835f2c3e2dSAndreas Boehler    $res = $sqlite->query($query,
684d5703f5aSAndreas Boehler            $calid,
685d5703f5aSAndreas Boehler            $uri,
686d5703f5aSAndreas Boehler            $ics,
687d5703f5aSAndreas Boehler            time(),
688d5703f5aSAndreas Boehler            $extraData['etag'],
689d5703f5aSAndreas Boehler            $extraData['size'],
690d5703f5aSAndreas Boehler            $extraData['componentType'],
691d5703f5aSAndreas Boehler            $extraData['firstOccurence'],
692d5703f5aSAndreas Boehler            $extraData['lastOccurence'],
693d5703f5aSAndreas Boehler            $extraData['uid']);
694d5703f5aSAndreas Boehler            // If successfully, update the sync token database
695d5703f5aSAndreas Boehler    if($res !== false)
696d5703f5aSAndreas Boehler    {
697d5703f5aSAndreas Boehler        $this->updateSyncTokenLog($calid, $uri, 'added');
698d5703f5aSAndreas Boehler    }
699d5703f5aSAndreas Boehler    return $extraData['etag'];
700d5703f5aSAndreas Boehler  }
701d5703f5aSAndreas Boehler
702d5703f5aSAndreas Boehler  /**
703d5703f5aSAndreas Boehler   * Edit a calendar entry by providing a new ICS file. This is mainly
704d5703f5aSAndreas Boehler   * needed for the sync support.
705d5703f5aSAndreas Boehler   *
706d5703f5aSAndreas Boehler   * @param int $calid The calendar's IS
707d5703f5aSAndreas Boehler   * @param string $uri The object's URI to modify
708d5703f5aSAndreas Boehler   * @param string $ics The new object's ICS file
709d5703f5aSAndreas Boehler   */
710d5703f5aSAndreas Boehler  public function editCalendarEntryToCalendarByICS($calid, $uri, $ics)
711d5703f5aSAndreas Boehler  {
712d5703f5aSAndreas Boehler      $extraData = $this->getDenormalizedData($ics);
713d5703f5aSAndreas Boehler
7145f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
7155f2c3e2dSAndreas Boehler      if(!$sqlite)
7165f2c3e2dSAndreas Boehler        return false;
717d5703f5aSAndreas Boehler      $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, etag = ?, size = ?, componenttype = ?, firstoccurence = ?, lastoccurence = ?, uid = ? WHERE calendarid = ? AND uri = ?";
7185f2c3e2dSAndreas Boehler      $res = $sqlite->query($query,
719d5703f5aSAndreas Boehler        $ics,
720d5703f5aSAndreas Boehler        time(),
721d5703f5aSAndreas Boehler        $extraData['etag'],
722d5703f5aSAndreas Boehler        $extraData['size'],
723d5703f5aSAndreas Boehler        $extraData['componentType'],
724d5703f5aSAndreas Boehler        $extraData['firstOccurence'],
725d5703f5aSAndreas Boehler        $extraData['lastOccurence'],
726d5703f5aSAndreas Boehler        $extraData['uid'],
727d5703f5aSAndreas Boehler        $calid,
728d5703f5aSAndreas Boehler        $uri
729d5703f5aSAndreas Boehler      );
730d5703f5aSAndreas Boehler      if($res !== false)
731d5703f5aSAndreas Boehler      {
732d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'modified');
733d5703f5aSAndreas Boehler      }
734d5703f5aSAndreas Boehler      return $extraData['etag'];
735d5703f5aSAndreas Boehler  }
736d5703f5aSAndreas Boehler
737d5703f5aSAndreas Boehler  /**
738cb71a62aSAndreas Boehler   * Add a new iCal entry for a given page, i.e. a given calendar.
739cb71a62aSAndreas Boehler   *
740cb71a62aSAndreas Boehler   * The parameter array needs to contain
741cb71a62aSAndreas Boehler   *   detectedtz       => The timezone as detected by the browser
74282a48dfbSAndreas Boehler   *   currenttz        => The timezone in use by the calendar
743cb71a62aSAndreas Boehler   *   eventfrom        => The event's start date
744cb71a62aSAndreas Boehler   *   eventfromtime    => The event's start time
745cb71a62aSAndreas Boehler   *   eventto          => The event's end date
746cb71a62aSAndreas Boehler   *   eventtotime      => The event's end time
747cb71a62aSAndreas Boehler   *   eventname        => The event's name
748cb71a62aSAndreas Boehler   *   eventdescription => The event's description
749cb71a62aSAndreas Boehler   *
750cb71a62aSAndreas Boehler   * @param string $id The page ID to work on
751cb71a62aSAndreas Boehler   * @param string $user The user who created the calendar
752cb71a62aSAndreas Boehler   * @param string $params A parameter array with values to create
753cb71a62aSAndreas Boehler   *
754cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
755cb71a62aSAndreas Boehler   */
756a1a3b679SAndreas Boehler  public function addCalendarEntryToCalendarForPage($id, $user, $params)
757a1a3b679SAndreas Boehler  {
75882a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
75982a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
76082a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
761a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
762bd883736SAndreas Boehler      else
763bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
764cb71a62aSAndreas Boehler
765cb71a62aSAndreas Boehler      // Retrieve dates from settings
766b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
767b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
768b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
769b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
770cb71a62aSAndreas Boehler
771cb71a62aSAndreas Boehler      // Load SabreDAV
7729bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
773a1a3b679SAndreas Boehler      $vcalendar = new \Sabre\VObject\Component\VCalendar();
774cb71a62aSAndreas Boehler
775cb71a62aSAndreas Boehler      // Add VCalendar, UID and Event Name
776a1a3b679SAndreas Boehler      $event = $vcalendar->add('VEVENT');
777b269830cSAndreas Boehler      $uuid = \Sabre\VObject\UUIDUtil::getUUID();
778b269830cSAndreas Boehler      $event->add('UID', $uuid);
779a1a3b679SAndreas Boehler      $event->summary = $params['eventname'];
780cb71a62aSAndreas Boehler
781cb71a62aSAndreas Boehler      // Add a description if requested
7820eebc909SAndreas Boehler      $description = $params['eventdescription'];
7830eebc909SAndreas Boehler      if($description !== '')
7840eebc909SAndreas Boehler        $event->add('DESCRIPTION', $description);
785cb71a62aSAndreas Boehler
7862b7be5bdSAndreas Boehler      // Add a location if requested
7872b7be5bdSAndreas Boehler      $location = $params['eventlocation'];
7882b7be5bdSAndreas Boehler      if($location !== '')
7892b7be5bdSAndreas Boehler        $event->add('LOCATION', $location);
7902b7be5bdSAndreas Boehler
7914ecb526cSAndreas Boehler      // Add attachments
7924ecb526cSAndreas Boehler      $attachments = $params['attachments'];
79382a48dfbSAndreas Boehler      if(!is_null($attachments))
7944ecb526cSAndreas Boehler        foreach($attachments as $attachment)
7954ecb526cSAndreas Boehler          $event->add('ATTACH', $attachment);
7964ecb526cSAndreas Boehler
797cb71a62aSAndreas Boehler      // Create a timestamp for last modified, created and dtstamp values in UTC
798b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
799b269830cSAndreas Boehler      $event->add('DTSTAMP', $dtStamp);
800b269830cSAndreas Boehler      $event->add('CREATED', $dtStamp);
801b269830cSAndreas Boehler      $event->add('LAST-MODIFIED', $dtStamp);
802cb71a62aSAndreas Boehler
803cb71a62aSAndreas Boehler      // Adjust the start date, based on the given timezone information
804b269830cSAndreas Boehler      $dtStart = new \DateTime();
805a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
806b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
807cb71a62aSAndreas Boehler
808cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
809b269830cSAndreas Boehler      if($params['allday'] != '1')
810b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
811cb71a62aSAndreas Boehler
812cb71a62aSAndreas Boehler      // Adjust the end date, based on the given timezone information
813b269830cSAndreas Boehler      $dtEnd = new \DateTime();
814a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
815b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
816cb71a62aSAndreas Boehler
817cb71a62aSAndreas Boehler      // Only add the time values if it's not an allday event
818b269830cSAndreas Boehler      if($params['allday'] != '1')
819b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
820cb71a62aSAndreas Boehler
821b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
822b269830cSAndreas Boehler      if($params['allday'] == '1')
823b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
824cb71a62aSAndreas Boehler
825cb71a62aSAndreas Boehler      // Really add Start and End events
826b269830cSAndreas Boehler      $dtStartEv = $event->add('DTSTART', $dtStart);
827b269830cSAndreas Boehler      $dtEndEv = $event->add('DTEND', $dtEnd);
828cb71a62aSAndreas Boehler
829cb71a62aSAndreas Boehler      // Adjust the DATE format for allday events
830b269830cSAndreas Boehler      if($params['allday'] == '1')
831b269830cSAndreas Boehler      {
832b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
833b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
834b269830cSAndreas Boehler      }
835cb71a62aSAndreas Boehler
836809cb0faSAndreas Boehler      $eventStr = $vcalendar->serialize();
837809cb0faSAndreas Boehler
838809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
839809cb0faSAndreas Boehler      {
840809cb0faSAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
841809cb0faSAndreas Boehler          if(is_null($wdc))
842809cb0faSAndreas Boehler            return false;
843809cb0faSAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
844809cb0faSAndreas Boehler          return $wdc->addCalendarEntry($connectionId, $eventStr);
845809cb0faSAndreas Boehler      }
846809cb0faSAndreas Boehler      else
847809cb0faSAndreas Boehler      {
848cb71a62aSAndreas Boehler          // Actually add the values to the database
849a1a3b679SAndreas Boehler          $calid = $this->getCalendarIdForPage($id);
85002eea9b8SAndreas Boehler          $uri = $uri = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics';
8511bb22c2bSAndreas Boehler          $now = new \DateTime();
852a1a3b679SAndreas Boehler
8535f2c3e2dSAndreas Boehler          $sqlite = $this->getDB();
8545f2c3e2dSAndreas Boehler          if(!$sqlite)
8555f2c3e2dSAndreas Boehler            return false;
85651f4febbSAndreas Boehler          $query = "INSERT INTO calendarobjects (calendarid, uri, calendardata, lastmodified, componenttype, firstoccurence, lastoccurence, size, etag, uid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
8575f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $calid, $uri, $eventStr, $now->getTimestamp(), 'VEVENT',
85851f4febbSAndreas Boehler                                      $event->DTSTART->getDateTime()->getTimeStamp(), $event->DTEND->getDateTime()->getTimeStamp(),
85951f4febbSAndreas Boehler                                      strlen($eventStr), md5($eventStr), $uuid);
860cb71a62aSAndreas Boehler
861cb71a62aSAndreas Boehler          // If successfully, update the sync token database
86255a741c0SAndreas Boehler          if($res !== false)
86355a741c0SAndreas Boehler          {
86455a741c0SAndreas Boehler              $this->updateSyncTokenLog($calid, $uri, 'added');
865a1a3b679SAndreas Boehler              return true;
866a1a3b679SAndreas Boehler          }
867809cb0faSAndreas Boehler      }
86855a741c0SAndreas Boehler      return false;
86955a741c0SAndreas Boehler  }
870a1a3b679SAndreas Boehler
871cb71a62aSAndreas Boehler  /**
872cb71a62aSAndreas Boehler   * Retrieve the calendar settings of a given calendar id
873cb71a62aSAndreas Boehler   *
874cb71a62aSAndreas Boehler   * @param string $calid The calendar ID
875cb71a62aSAndreas Boehler   *
876cb71a62aSAndreas Boehler   * @return array The calendar settings array
877cb71a62aSAndreas Boehler   */
878b269830cSAndreas Boehler  public function getCalendarSettings($calid)
879b269830cSAndreas Boehler  {
8805f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
8815f2c3e2dSAndreas Boehler      if(!$sqlite)
8825f2c3e2dSAndreas Boehler        return false;
883d99db92eSscottleechua      $query = "SELECT id, principaluri, calendarcolor, displayname, uri, description, components, transparent, synctoken, disabled, timezone FROM calendars WHERE id= ? ";
88461fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$calid]);
885b269830cSAndreas Boehler      return $row;
886b269830cSAndreas Boehler  }
887b269830cSAndreas Boehler
888cb71a62aSAndreas Boehler  /**
88913b16484SAndreas Boehler   * Retrieve the calendar status of a given calendar id
89013b16484SAndreas Boehler   *
89113b16484SAndreas Boehler   * @param string $calid The calendar ID
89213b16484SAndreas Boehler   * @return boolean True if calendar is enabled, otherwise false
89313b16484SAndreas Boehler   */
89413b16484SAndreas Boehler  public function getCalendarStatus($calid)
89513b16484SAndreas Boehler  {
8965f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
8975f2c3e2dSAndreas Boehler      if(!$sqlite)
8985f2c3e2dSAndreas Boehler        return false;
89913b16484SAndreas Boehler      $query = "SELECT disabled FROM calendars WHERE id = ?";
90061fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$calid]);
90161fec75eSscottleechua      if($row && $row['disabled'] == 1)
90213b16484SAndreas Boehler        return false;
90313b16484SAndreas Boehler      else
90413b16484SAndreas Boehler        return true;
90513b16484SAndreas Boehler  }
90613b16484SAndreas Boehler
90713b16484SAndreas Boehler  /**
90813b16484SAndreas Boehler   * Disable a calendar for a given page
90913b16484SAndreas Boehler   *
91013b16484SAndreas Boehler   * @param string $id The page ID
91113b16484SAndreas Boehler   *
91213b16484SAndreas Boehler   * @return boolean true on success, otherwise false
91313b16484SAndreas Boehler   */
91413b16484SAndreas Boehler  public function disableCalendarForPage($id)
91513b16484SAndreas Boehler  {
91613b16484SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
91713b16484SAndreas Boehler      if($calid === false)
91813b16484SAndreas Boehler        return false;
9195f2c3e2dSAndreas Boehler
9205f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9215f2c3e2dSAndreas Boehler      if(!$sqlite)
9225f2c3e2dSAndreas Boehler        return false;
92313b16484SAndreas Boehler      $query = "UPDATE calendars SET disabled = 1 WHERE id = ?";
9245f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
92513b16484SAndreas Boehler      if($res !== false)
92613b16484SAndreas Boehler        return true;
92713b16484SAndreas Boehler      return false;
92813b16484SAndreas Boehler  }
92913b16484SAndreas Boehler
93013b16484SAndreas Boehler  /**
93113b16484SAndreas Boehler   * Enable a calendar for a given page
93213b16484SAndreas Boehler   *
93313b16484SAndreas Boehler   * @param string $id The page ID
93413b16484SAndreas Boehler   *
93513b16484SAndreas Boehler   * @return boolean true on success, otherwise false
93613b16484SAndreas Boehler   */
93713b16484SAndreas Boehler  public function enableCalendarForPage($id)
93813b16484SAndreas Boehler  {
93913b16484SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
94013b16484SAndreas Boehler      if($calid === false)
94113b16484SAndreas Boehler        return false;
9425f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9435f2c3e2dSAndreas Boehler      if(!$sqlite)
9445f2c3e2dSAndreas Boehler        return false;
94513b16484SAndreas Boehler      $query = "UPDATE calendars SET disabled = 0 WHERE id = ?";
9465f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid);
94713b16484SAndreas Boehler      if($res !== false)
94813b16484SAndreas Boehler        return true;
94913b16484SAndreas Boehler      return false;
95013b16484SAndreas Boehler  }
95113b16484SAndreas Boehler
95213b16484SAndreas Boehler  /**
953cb71a62aSAndreas Boehler   * Retrieve all events that are within a given date range,
954cb71a62aSAndreas Boehler   * based on the timezone setting.
955cb71a62aSAndreas Boehler   *
956cb71a62aSAndreas Boehler   * There is also support for retrieving recurring events,
957cb71a62aSAndreas Boehler   * using Sabre's VObject Iterator. Recurring events are represented
958cb71a62aSAndreas Boehler   * as individual calendar entries with the same UID.
959cb71a62aSAndreas Boehler   *
960cb71a62aSAndreas Boehler   * @param string $id The page ID to work with
961cb71a62aSAndreas Boehler   * @param string $user The user ID to work with
962cb71a62aSAndreas Boehler   * @param string $startDate The start date as a string
963cb71a62aSAndreas Boehler   * @param string $endDate The end date as a string
9644a2bf5eeSAndreas Boehler   * @param string $color (optional) The calendar's color
965cb71a62aSAndreas Boehler   *
966cb71a62aSAndreas Boehler   * @return array An array containing the calendar entries.
967cb71a62aSAndreas Boehler   */
9684a2bf5eeSAndreas Boehler  public function getEventsWithinDateRange($id, $user, $startDate, $endDate, $timezone, $color = null)
969a1a3b679SAndreas Boehler  {
97082a48dfbSAndreas Boehler      if($timezone !== '' && $timezone !== 'local')
97182a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($timezone);
972bd883736SAndreas Boehler      else
973bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
974a1a3b679SAndreas Boehler      $data = array();
975b8265976SAndreas Boehler      $calname = 'unknown';
976cb71a62aSAndreas Boehler
9775f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
9785f2c3e2dSAndreas Boehler      if(!$sqlite)
9795f2c3e2dSAndreas Boehler        return false;
9805f2c3e2dSAndreas Boehler
98161fec75eSscottleechua      $calid = $this->getCalendarIdForPage($id);
982a469597cSAndreas Boehler      $query = "SELECT calendardata, componenttype, uid FROM calendarobjects WHERE calendarid = ?";
9837e2d2436Sscottleechua      $params = array($calid);
984a469597cSAndreas Boehler      $startTs = null;
985a469597cSAndreas Boehler      $endTs = null;
986a469597cSAndreas Boehler      if($startDate !== null)
987a469597cSAndreas Boehler      {
988a1a3b679SAndreas Boehler        $startTs = new \DateTime($startDate);
9897e2d2436Sscottleechua        $query .= " AND lastoccurence > ?";
9907e2d2436Sscottleechua        $params[] = $startTs->getTimestamp();
991a469597cSAndreas Boehler      }
992a469597cSAndreas Boehler      if($endDate !== null)
993a469597cSAndreas Boehler      {
994a1a3b679SAndreas Boehler        $endTs = new \DateTime($endDate);
9957e2d2436Sscottleechua        $query .= " AND firstoccurence < ?";
9967e2d2436Sscottleechua        $params[] = $endTs->getTimestamp();
997a469597cSAndreas Boehler      }
998cb71a62aSAndreas Boehler
9990b805092SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
10000b805092SAndreas Boehler
10010b805092SAndreas Boehler      if(strpos($id, 'webdav://') === 0)
10020b805092SAndreas Boehler      {
10030b805092SAndreas Boehler          $wdc =& plugin_load('helper', 'webdavclient');
10040b805092SAndreas Boehler          if(is_null($wdc))
10050b805092SAndreas Boehler            return $data;
10060b805092SAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
10070b805092SAndreas Boehler          $arr = $wdc->getCalendarEntries($connectionId, $startDate, $endDate);
1008b8265976SAndreas Boehler          $conn = $wdc->getConnection($connectionId);
1009b8265976SAndreas Boehler          $calname = $conn['displayname'];
10100b805092SAndreas Boehler      }
10110b805092SAndreas Boehler      else
10120b805092SAndreas Boehler      {
10130b805092SAndreas Boehler          if(is_null($color))
10140b805092SAndreas Boehler            $color = $this->getCalendarColorForCalendar($calid);
10150b805092SAndreas Boehler
101659b68239SAndreas Boehler          $enabled = $this->getCalendarStatus($calid);
101759b68239SAndreas Boehler          if($enabled === false)
101859b68239SAndreas Boehler            return $data;
101959b68239SAndreas Boehler
1020b8265976SAndreas Boehler          $settings = $this->getCalendarSettings($calid);
1021b8265976SAndreas Boehler          $calname = $settings['displayname'];
1022b8265976SAndreas Boehler
102361fec75eSscottleechua          $arr = $sqlite->queryAll($query, $params);
10240b805092SAndreas Boehler      }
1025cb71a62aSAndreas Boehler
1026a1a3b679SAndreas Boehler      foreach($arr as $row)
1027a1a3b679SAndreas Boehler      {
1028a1a3b679SAndreas Boehler          if(isset($row['calendardata']))
1029a1a3b679SAndreas Boehler          {
1030b269830cSAndreas Boehler              $entry = array();
1031a1a3b679SAndreas Boehler              $vcal = \Sabre\VObject\Reader::read($row['calendardata']);
1032ebc4eb57SAndreas Boehler              $recurrence = $vcal->VEVENT->RRULE;
1033ebc4eb57SAndreas Boehler              if($recurrence != null)
1034ebc4eb57SAndreas Boehler              {
1035ebc4eb57SAndreas Boehler                  $rEvents = new \Sabre\VObject\Recur\EventIterator(array($vcal->VEVENT));
1036ebc4eb57SAndreas Boehler                  $rEvents->rewind();
1037e9b7d302SAndreas Boehler                  while($rEvents->valid())
1038ebc4eb57SAndreas Boehler                  {
1039ebc4eb57SAndreas Boehler                      $event = $rEvents->getEventObject();
1040a469597cSAndreas Boehler                      if(($endTs !== null) && ($rEvents->getDtStart()->getTimestamp() > $endTs->getTimestamp()))
1041e9b7d302SAndreas Boehler                          break;
1042a469597cSAndreas Boehler                      if(($startTs != null) && ($rEvents->getDtEnd()->getTimestamp() < $startTs->getTimestamp()))
1043ebc4eb57SAndreas Boehler                      {
1044ebc4eb57SAndreas Boehler                          $rEvents->next();
1045ebc4eb57SAndreas Boehler                          continue;
1046ebc4eb57SAndreas Boehler                      }
1047b8265976SAndreas Boehler                      $data[] = array_merge(array('calendarname' => $calname),
1048b8265976SAndreas Boehler                                            $this->convertIcalDataToEntry($event, $id, $timezone, $row['uid'], $color, true));
1049ebc4eb57SAndreas Boehler                      $rEvents->next();
1050ebc4eb57SAndreas Boehler                  }
1051ebc4eb57SAndreas Boehler              }
1052ebc4eb57SAndreas Boehler              else
1053b8265976SAndreas Boehler                $data[] = array_merge(array('calendarname' => $calname),
1054b8265976SAndreas Boehler                                      $this->convertIcalDataToEntry($vcal->VEVENT, $id, $timezone, $row['uid'], $color));
1055ebc4eb57SAndreas Boehler          }
1056ebc4eb57SAndreas Boehler      }
1057ebc4eb57SAndreas Boehler      return $data;
1058ebc4eb57SAndreas Boehler  }
1059ebc4eb57SAndreas Boehler
1060cb71a62aSAndreas Boehler  /**
1061cb71a62aSAndreas Boehler   * Helper function that parses the iCal data of a VEVENT to a calendar entry.
1062cb71a62aSAndreas Boehler   *
1063cb71a62aSAndreas Boehler   * @param \Sabre\VObject\VEvent $event The event to parse
1064cb71a62aSAndreas Boehler   * @param \DateTimeZone $timezone The timezone object
1065cb71a62aSAndreas Boehler   * @param string $uid The entry's UID
10663c86dda8SAndreas Boehler   * @param boolean $recurring (optional) Set to true to define a recurring event
1067cb71a62aSAndreas Boehler   *
1068cb71a62aSAndreas Boehler   * @return array The parse calendar entry
1069cb71a62aSAndreas Boehler   */
1070185e2535SAndreas Boehler  private function convertIcalDataToEntry($event, $page, $timezone, $uid, $color, $recurring = false)
1071ebc4eb57SAndreas Boehler  {
1072ebc4eb57SAndreas Boehler      $entry = array();
1073ebc4eb57SAndreas Boehler      $start = $event->DTSTART;
1074cb71a62aSAndreas Boehler      // Parse only if the start date/time is present
1075b269830cSAndreas Boehler      if($start !== null)
1076b269830cSAndreas Boehler      {
1077b269830cSAndreas Boehler        $dtStart = $start->getDateTime();
1078b269830cSAndreas Boehler        $dtStart->setTimezone($timezone);
1079bf0ad2b4SAndreas Boehler
1080bf0ad2b4SAndreas Boehler        // moment.js doesn't like times be given even if
1081bf0ad2b4SAndreas Boehler        // allDay is set to true
1082bf0ad2b4SAndreas Boehler        // This should fix T23
1083b269830cSAndreas Boehler        if($start['VALUE'] == 'DATE')
1084bf0ad2b4SAndreas Boehler        {
1085b269830cSAndreas Boehler          $entry['allDay'] = true;
1086bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format("Y-m-d");
1087bf0ad2b4SAndreas Boehler        }
1088b269830cSAndreas Boehler        else
1089bf0ad2b4SAndreas Boehler        {
1090b269830cSAndreas Boehler          $entry['allDay'] = false;
1091bf0ad2b4SAndreas Boehler          $entry['start'] = $dtStart->format(\DateTime::ATOM);
1092bf0ad2b4SAndreas Boehler        }
1093b269830cSAndreas Boehler      }
1094ebc4eb57SAndreas Boehler      $end = $event->DTEND;
1095bf0ad2b4SAndreas Boehler      // Parse only if the end date/time is present
1096b269830cSAndreas Boehler      if($end !== null)
1097b269830cSAndreas Boehler      {
1098b269830cSAndreas Boehler        $dtEnd = $end->getDateTime();
1099b269830cSAndreas Boehler        $dtEnd->setTimezone($timezone);
1100bf0ad2b4SAndreas Boehler        if($end['VALUE'] == 'DATE')
1101bf0ad2b4SAndreas Boehler          $entry['end'] = $dtEnd->format("Y-m-d");
1102bf0ad2b4SAndreas Boehler        else
1103b269830cSAndreas Boehler          $entry['end'] = $dtEnd->format(\DateTime::ATOM);
1104b269830cSAndreas Boehler      }
1105954f1ffaSAndreas Boehler      $duration = $event->DURATION;
1106954f1ffaSAndreas Boehler      // Parse duration only if start is set, but end is missing
1107954f1ffaSAndreas Boehler      if($start !== null && $end == null && $duration !== null)
1108954f1ffaSAndreas Boehler      {
1109954f1ffaSAndreas Boehler          $interval = $duration->getDateInterval();
1110954f1ffaSAndreas Boehler          $dtStart = $start->getDateTime();
1111954f1ffaSAndreas Boehler          $dtStart->setTimezone($timezone);
1112954f1ffaSAndreas Boehler          $dtEnd = $dtStart->add($interval);
1113954f1ffaSAndreas Boehler          $dtEnd->setTimezone($timezone);
1114954f1ffaSAndreas Boehler          $entry['end'] = $dtEnd->format(\DateTime::ATOM);
1115954f1ffaSAndreas Boehler      }
1116ebc4eb57SAndreas Boehler      $description = $event->DESCRIPTION;
11170eebc909SAndreas Boehler      if($description !== null)
11180eebc909SAndreas Boehler        $entry['description'] = (string)$description;
11190eebc909SAndreas Boehler      else
11200eebc909SAndreas Boehler        $entry['description'] = '';
11214ecb526cSAndreas Boehler      $attachments = $event->ATTACH;
11224ecb526cSAndreas Boehler      if($attachments !== null)
11234ecb526cSAndreas Boehler      {
11244ecb526cSAndreas Boehler        $entry['attachments'] = array();
11254ecb526cSAndreas Boehler        foreach($attachments as $attachment)
11264ecb526cSAndreas Boehler          $entry['attachments'][] = (string)$attachment;
11274ecb526cSAndreas Boehler      }
1128ebc4eb57SAndreas Boehler      $entry['title'] = (string)$event->summary;
11292b7be5bdSAndreas Boehler      $entry['location'] = (string)$event->location;
1130ebc4eb57SAndreas Boehler      $entry['id'] = $uid;
1131185e2535SAndreas Boehler      $entry['page'] = $page;
1132185e2535SAndreas Boehler      $entry['color'] = $color;
11333c86dda8SAndreas Boehler      $entry['recurring'] = $recurring;
1134185e2535SAndreas Boehler
1135ebc4eb57SAndreas Boehler      return $entry;
1136a1a3b679SAndreas Boehler  }
1137a1a3b679SAndreas Boehler
1138cb71a62aSAndreas Boehler  /**
1139cb71a62aSAndreas Boehler   * Retrieve an event by its UID
1140cb71a62aSAndreas Boehler   *
1141cb71a62aSAndreas Boehler   * @param string $uid The event's UID
1142cb71a62aSAndreas Boehler   *
1143cb71a62aSAndreas Boehler   * @return mixed The table row with the given event
1144cb71a62aSAndreas Boehler   */
1145a1a3b679SAndreas Boehler  public function getEventWithUid($uid)
1146a1a3b679SAndreas Boehler  {
11475f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11485f2c3e2dSAndreas Boehler      if(!$sqlite)
11495f2c3e2dSAndreas Boehler        return false;
115051f4febbSAndreas Boehler      $query = "SELECT calendardata, calendarid, componenttype, uri FROM calendarobjects WHERE uid = ?";
115161fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$uid]);
1152a1a3b679SAndreas Boehler      return $row;
1153a1a3b679SAndreas Boehler  }
1154a1a3b679SAndreas Boehler
1155cb71a62aSAndreas Boehler  /**
1156d5703f5aSAndreas Boehler   * Retrieve information of a calendar's object, not including the actual
115759b68239SAndreas Boehler   * calendar data! This is mainly needed for the sync support.
1158d5703f5aSAndreas Boehler   *
1159d5703f5aSAndreas Boehler   * @param int $calid The calendar ID
1160d5703f5aSAndreas Boehler   *
1161d5703f5aSAndreas Boehler   * @return mixed The result
1162d5703f5aSAndreas Boehler   */
1163d5703f5aSAndreas Boehler  public function getCalendarObjects($calid)
1164d5703f5aSAndreas Boehler  {
11655f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11665f2c3e2dSAndreas Boehler      if(!$sqlite)
11675f2c3e2dSAndreas Boehler        return false;
1168d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, componenttype FROM calendarobjects WHERE calendarid = ?";
116961fec75eSscottleechua      $arr = $sqlite->queryAll($query, [$calid]);
1170d5703f5aSAndreas Boehler      return $arr;
1171d5703f5aSAndreas Boehler  }
1172d5703f5aSAndreas Boehler
1173d5703f5aSAndreas Boehler  /**
1174d5703f5aSAndreas Boehler   * Retrieve a single calendar object by calendar ID and URI
1175d5703f5aSAndreas Boehler   *
1176d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1177d5703f5aSAndreas Boehler   * @param string $uri The object's URI
1178d5703f5aSAndreas Boehler   *
1179d5703f5aSAndreas Boehler   * @return mixed The result
1180d5703f5aSAndreas Boehler   */
1181d5703f5aSAndreas Boehler  public function getCalendarObjectByUri($calid, $uri)
1182d5703f5aSAndreas Boehler  {
11835f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
11845f2c3e2dSAndreas Boehler      if(!$sqlite)
11855f2c3e2dSAndreas Boehler        return false;
1186d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri = ?";
118761fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$calid, $uri]);
1188d5703f5aSAndreas Boehler      return $row;
1189d5703f5aSAndreas Boehler  }
1190d5703f5aSAndreas Boehler
1191d5703f5aSAndreas Boehler  /**
1192d5703f5aSAndreas Boehler   * Retrieve several calendar objects by specifying an array of URIs.
1193d5703f5aSAndreas Boehler   * This is mainly neede for sync.
1194d5703f5aSAndreas Boehler   *
1195d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1196d5703f5aSAndreas Boehler   * @param array $uris An array of URIs
1197d5703f5aSAndreas Boehler   *
1198d5703f5aSAndreas Boehler   * @return mixed The result
1199d5703f5aSAndreas Boehler   */
1200d5703f5aSAndreas Boehler  public function getMultipleCalendarObjectsByUri($calid, $uris)
1201d5703f5aSAndreas Boehler  {
12025f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
12035f2c3e2dSAndreas Boehler      if(!$sqlite)
12045f2c3e2dSAndreas Boehler        return false;
1205d5703f5aSAndreas Boehler      $query = "SELECT id, uri, lastmodified, etag, calendarid, size, calendardata, componenttype FROM calendarobjects WHERE calendarid = ? AND uri IN (";
1206d5703f5aSAndreas Boehler      $query .= implode(',', array_fill(0, count($uris), '?'));
1207d5703f5aSAndreas Boehler      $query .= ')';
1208d5703f5aSAndreas Boehler      $vals = array_merge(array($calid), $uris);
120961fec75eSscottleechua      $arr = $sqlite->queryAll($query, $vals);
1210d5703f5aSAndreas Boehler      return $arr;
1211d5703f5aSAndreas Boehler  }
1212d5703f5aSAndreas Boehler
1213d5703f5aSAndreas Boehler  /**
1214cb71a62aSAndreas Boehler   * Retrieve all calendar events for a given calendar ID
1215cb71a62aSAndreas Boehler   *
1216cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
1217cb71a62aSAndreas Boehler   *
1218cb71a62aSAndreas Boehler   * @return array An array containing all calendar data
1219cb71a62aSAndreas Boehler   */
1220f69bb449SAndreas Boehler  public function getAllCalendarEvents($calid)
1221f69bb449SAndreas Boehler  {
122259b68239SAndreas Boehler      $enabled = $this->getCalendarStatus($calid);
122359b68239SAndreas Boehler      if($enabled === false)
122459b68239SAndreas Boehler        return false;
12255f2c3e2dSAndreas Boehler
12265f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
12275f2c3e2dSAndreas Boehler      if(!$sqlite)
12285f2c3e2dSAndreas Boehler        return false;
12297e0b8590SAndreas Boehler      $query = "SELECT calendardata, uid, componenttype, uri FROM calendarobjects WHERE calendarid = ?";
123061fec75eSscottleechua      $arr = $sqlite->queryAll($query, [$calid]);
1231f69bb449SAndreas Boehler      return $arr;
1232f69bb449SAndreas Boehler  }
1233f69bb449SAndreas Boehler
1234cb71a62aSAndreas Boehler  /**
1235cb71a62aSAndreas Boehler   * Edit a calendar entry for a page, given by its parameters.
1236cb71a62aSAndreas Boehler   * The params array has the same format as @see addCalendarEntryForPage
1237cb71a62aSAndreas Boehler   *
1238cb71a62aSAndreas Boehler   * @param string $id The page's ID to work on
1239cb71a62aSAndreas Boehler   * @param string $user The user's ID to work on
1240cb71a62aSAndreas Boehler   * @param array $params The parameter array for the edited calendar event
1241cb71a62aSAndreas Boehler   *
1242cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1243cb71a62aSAndreas Boehler   */
1244a1a3b679SAndreas Boehler  public function editCalendarEntryForPage($id, $user, $params)
1245a1a3b679SAndreas Boehler  {
124682a48dfbSAndreas Boehler      if($params['currenttz'] !== '' && $params['currenttz'] !== 'local')
124782a48dfbSAndreas Boehler          $timezone = new \DateTimeZone($params['currenttz']);
124882a48dfbSAndreas Boehler      elseif($params['currenttz'] === 'local')
1249a25c89eaSAndreas Boehler          $timezone = new \DateTimeZone($params['detectedtz']);
1250bd883736SAndreas Boehler      else
1251bd883736SAndreas Boehler          $timezone = new \DateTimeZone('UTC');
1252cb71a62aSAndreas Boehler
1253cb71a62aSAndreas Boehler      // Parse dates
1254b269830cSAndreas Boehler      $startDate = explode('-', $params['eventfrom']);
1255b269830cSAndreas Boehler      $startTime = explode(':', $params['eventfromtime']);
1256b269830cSAndreas Boehler      $endDate = explode('-', $params['eventto']);
1257b269830cSAndreas Boehler      $endTime = explode(':', $params['eventtotime']);
1258cb71a62aSAndreas Boehler
1259cb71a62aSAndreas Boehler      // Retrieve the existing event based on the UID
126055a741c0SAndreas Boehler      $uid = $params['uid'];
1261809cb0faSAndreas Boehler
1262809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1263809cb0faSAndreas Boehler      {
1264809cb0faSAndreas Boehler        $wdc =& plugin_load('helper', 'webdavclient');
1265809cb0faSAndreas Boehler        if(is_null($wdc))
1266809cb0faSAndreas Boehler          return false;
1267809cb0faSAndreas Boehler        $event = $wdc->getCalendarEntryByUid($uid);
1268809cb0faSAndreas Boehler      }
1269809cb0faSAndreas Boehler      else
1270809cb0faSAndreas Boehler      {
127155a741c0SAndreas Boehler        $event = $this->getEventWithUid($uid);
1272809cb0faSAndreas Boehler      }
1273cb71a62aSAndreas Boehler
1274cb71a62aSAndreas Boehler      // Load SabreDAV
12759bef4ad8SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1276a1a3b679SAndreas Boehler      if(!isset($event['calendardata']))
1277a1a3b679SAndreas Boehler        return false;
127855a741c0SAndreas Boehler      $uri = $event['uri'];
127955a741c0SAndreas Boehler      $calid = $event['calendarid'];
1280cb71a62aSAndreas Boehler
1281cb71a62aSAndreas Boehler      // Parse the existing event
1282a1a3b679SAndreas Boehler      $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
1283b269830cSAndreas Boehler      $vevent = $vcal->VEVENT;
1284cb71a62aSAndreas Boehler
1285cb71a62aSAndreas Boehler      // Set the new event values
1286b269830cSAndreas Boehler      $vevent->summary = $params['eventname'];
1287b269830cSAndreas Boehler      $dtStamp = new \DateTime(null, new \DateTimeZone('UTC'));
12880eebc909SAndreas Boehler      $description = $params['eventdescription'];
12892b7be5bdSAndreas Boehler      $location = $params['eventlocation'];
1290cb71a62aSAndreas Boehler
1291cb71a62aSAndreas Boehler      // Remove existing timestamps to overwrite them
12920eebc909SAndreas Boehler      $vevent->remove('DESCRIPTION');
1293b269830cSAndreas Boehler      $vevent->remove('DTSTAMP');
1294b269830cSAndreas Boehler      $vevent->remove('LAST-MODIFIED');
12954ecb526cSAndreas Boehler      $vevent->remove('ATTACH');
12962b7be5bdSAndreas Boehler      $vevent->remove('LOCATION');
1297cb71a62aSAndreas Boehler
12982b7be5bdSAndreas Boehler      // Add new time stamps, description and location
1299b269830cSAndreas Boehler      $vevent->add('DTSTAMP', $dtStamp);
1300b269830cSAndreas Boehler      $vevent->add('LAST-MODIFIED', $dtStamp);
13010eebc909SAndreas Boehler      if($description !== '')
13020eebc909SAndreas Boehler        $vevent->add('DESCRIPTION', $description);
13032b7be5bdSAndreas Boehler      if($location !== '')
13042b7be5bdSAndreas Boehler        $vevent->add('LOCATION', $location);
1305cb71a62aSAndreas Boehler
13064ecb526cSAndreas Boehler      // Add attachments
13074ecb526cSAndreas Boehler      $attachments = $params['attachments'];
130882a48dfbSAndreas Boehler      if(!is_null($attachments))
13094ecb526cSAndreas Boehler        foreach($attachments as $attachment)
13104ecb526cSAndreas Boehler          $vevent->add('ATTACH', $attachment);
13114ecb526cSAndreas Boehler
1312cb71a62aSAndreas Boehler      // Setup DTSTART
1313b269830cSAndreas Boehler      $dtStart = new \DateTime();
1314a25c89eaSAndreas Boehler      $dtStart->setTimezone($timezone);
1315b269830cSAndreas Boehler      $dtStart->setDate(intval($startDate[0]), intval($startDate[1]), intval($startDate[2]));
1316b269830cSAndreas Boehler      if($params['allday'] != '1')
1317b269830cSAndreas Boehler        $dtStart->setTime(intval($startTime[0]), intval($startTime[1]), 0);
1318cb71a62aSAndreas Boehler
13194ecb526cSAndreas Boehler      // Setup DTEND
1320b269830cSAndreas Boehler      $dtEnd = new \DateTime();
1321a25c89eaSAndreas Boehler      $dtEnd->setTimezone($timezone);
1322b269830cSAndreas Boehler      $dtEnd->setDate(intval($endDate[0]), intval($endDate[1]), intval($endDate[2]));
1323b269830cSAndreas Boehler      if($params['allday'] != '1')
1324b269830cSAndreas Boehler        $dtEnd->setTime(intval($endTime[0]), intval($endTime[1]), 0);
1325cb71a62aSAndreas Boehler
1326b269830cSAndreas Boehler      // According to the VCal spec, we need to add a whole day here
1327b269830cSAndreas Boehler      if($params['allday'] == '1')
1328b269830cSAndreas Boehler          $dtEnd->add(new \DateInterval('P1D'));
1329b269830cSAndreas Boehler      $vevent->remove('DTSTART');
1330b269830cSAndreas Boehler      $vevent->remove('DTEND');
1331b269830cSAndreas Boehler      $dtStartEv = $vevent->add('DTSTART', $dtStart);
1332b269830cSAndreas Boehler      $dtEndEv = $vevent->add('DTEND', $dtEnd);
1333cb71a62aSAndreas Boehler
1334cb71a62aSAndreas Boehler      // Remove the time for allday events
1335b269830cSAndreas Boehler      if($params['allday'] == '1')
1336b269830cSAndreas Boehler      {
1337b269830cSAndreas Boehler          $dtStartEv['VALUE'] = 'DATE';
1338b269830cSAndreas Boehler          $dtEndEv['VALUE'] = 'DATE';
1339b269830cSAndreas Boehler      }
1340a1a3b679SAndreas Boehler      $eventStr = $vcal->serialize();
1341809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1342809cb0faSAndreas Boehler      {
1343809cb0faSAndreas Boehler          $connectionId = str_replace('webdav://', '', $id);
1344809cb0faSAndreas Boehler          return $wdc->editCalendarEntry($connectionId, $uid, $eventStr);
1345809cb0faSAndreas Boehler      }
1346809cb0faSAndreas Boehler      else
1347809cb0faSAndreas Boehler      {
13485f2c3e2dSAndreas Boehler          $sqlite = $this->getDB();
13495f2c3e2dSAndreas Boehler          if(!$sqlite)
13505f2c3e2dSAndreas Boehler            return false;
1351809cb0faSAndreas Boehler          $now = new DateTime();
1352cb71a62aSAndreas Boehler          // Actually write to the database
135351f4febbSAndreas Boehler          $query = "UPDATE calendarobjects SET calendardata = ?, lastmodified = ?, ".
135451f4febbSAndreas Boehler                   "firstoccurence = ?, lastoccurence = ?, size = ?, etag = ? WHERE uid = ?";
13555f2c3e2dSAndreas Boehler          $res = $sqlite->query($query, $eventStr, $now->getTimestamp(), $dtStart->getTimestamp(),
135651f4febbSAndreas Boehler                                      $dtEnd->getTimestamp(), strlen($eventStr), md5($eventStr), $uid);
135755a741c0SAndreas Boehler          if($res !== false)
135855a741c0SAndreas Boehler          {
135955a741c0SAndreas Boehler              $this->updateSyncTokenLog($calid, $uri, 'modified');
1360a1a3b679SAndreas Boehler              return true;
1361a1a3b679SAndreas Boehler          }
1362809cb0faSAndreas Boehler      }
136355a741c0SAndreas Boehler      return false;
136455a741c0SAndreas Boehler  }
1365a1a3b679SAndreas Boehler
1366cb71a62aSAndreas Boehler  /**
1367d5703f5aSAndreas Boehler   * Delete an event from a calendar by calendar ID and URI
1368d5703f5aSAndreas Boehler   *
1369d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1370d5703f5aSAndreas Boehler   * @param string $uri The object's URI
1371d5703f5aSAndreas Boehler   *
1372d5703f5aSAndreas Boehler   * @return true
1373d5703f5aSAndreas Boehler   */
1374d5703f5aSAndreas Boehler  public function deleteCalendarEntryForCalendarByUri($calid, $uri)
1375d5703f5aSAndreas Boehler  {
13765f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
13775f2c3e2dSAndreas Boehler      if(!$sqlite)
13785f2c3e2dSAndreas Boehler        return false;
1379d5703f5aSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE calendarid = ? AND uri = ?";
13805f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $calid, $uri);
1381d5703f5aSAndreas Boehler      if($res !== false)
1382d5703f5aSAndreas Boehler      {
1383d5703f5aSAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
1384d5703f5aSAndreas Boehler      }
1385d5703f5aSAndreas Boehler      return true;
1386d5703f5aSAndreas Boehler  }
1387d5703f5aSAndreas Boehler
1388d5703f5aSAndreas Boehler  /**
1389cb71a62aSAndreas Boehler   * Delete a calendar entry for a given page. Actually, the event is removed
1390cb71a62aSAndreas Boehler   * based on the entry's UID, so that page ID is no used.
1391cb71a62aSAndreas Boehler   *
1392cb71a62aSAndreas Boehler   * @param string $id The page's ID (unused)
1393cb71a62aSAndreas Boehler   * @param array $params The parameter array to work with
1394cb71a62aSAndreas Boehler   *
1395cb71a62aSAndreas Boehler   * @return boolean True
1396cb71a62aSAndreas Boehler   */
1397a1a3b679SAndreas Boehler  public function deleteCalendarEntryForPage($id, $params)
1398a1a3b679SAndreas Boehler  {
1399a1a3b679SAndreas Boehler      $uid = $params['uid'];
1400809cb0faSAndreas Boehler      if(strpos($id, 'webdav://') === 0)
1401809cb0faSAndreas Boehler      {
1402809cb0faSAndreas Boehler        $wdc =& plugin_load('helper', 'webdavclient');
1403809cb0faSAndreas Boehler        if(is_null($wdc))
1404809cb0faSAndreas Boehler          return false;
1405809cb0faSAndreas Boehler        $connectionId = str_replace('webdav://', '', $id);
1406809cb0faSAndreas Boehler        $result = $wdc->deleteCalendarEntry($connectionId, $uid);
1407809cb0faSAndreas Boehler        return $result;
1408809cb0faSAndreas Boehler      }
14095f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
14105f2c3e2dSAndreas Boehler      if(!$sqlite)
14115f2c3e2dSAndreas Boehler        return false;
141255a741c0SAndreas Boehler      $event = $this->getEventWithUid($uid);
14132c14b82bSAndreas Boehler      $calid = $event['calendarid'];
141455a741c0SAndreas Boehler      $uri = $event['uri'];
141551f4febbSAndreas Boehler      $query = "DELETE FROM calendarobjects WHERE uid = ?";
14165f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $uid);
141755a741c0SAndreas Boehler      if($res !== false)
141855a741c0SAndreas Boehler      {
141955a741c0SAndreas Boehler          $this->updateSyncTokenLog($calid, $uri, 'deleted');
142055a741c0SAndreas Boehler      }
1421a1a3b679SAndreas Boehler      return true;
1422a1a3b679SAndreas Boehler  }
1423a1a3b679SAndreas Boehler
1424cb71a62aSAndreas Boehler  /**
1425cb71a62aSAndreas Boehler   * Retrieve the current sync token for a calendar
1426cb71a62aSAndreas Boehler   *
1427cb71a62aSAndreas Boehler   * @param string $calid The calendar id
1428cb71a62aSAndreas Boehler   *
1429cb71a62aSAndreas Boehler   * @return mixed The synctoken or false
1430cb71a62aSAndreas Boehler   */
143155a741c0SAndreas Boehler  public function getSyncTokenForCalendar($calid)
143255a741c0SAndreas Boehler  {
1433b269830cSAndreas Boehler      $row = $this->getCalendarSettings($calid);
143455a741c0SAndreas Boehler      if(isset($row['synctoken']))
143555a741c0SAndreas Boehler          return $row['synctoken'];
143655a741c0SAndreas Boehler      return false;
143755a741c0SAndreas Boehler  }
143855a741c0SAndreas Boehler
1439cb71a62aSAndreas Boehler  /**
1440cb71a62aSAndreas Boehler   * Helper function to convert the operation name to
1441cb71a62aSAndreas Boehler   * an operation code as stored in the database
1442cb71a62aSAndreas Boehler   *
1443cb71a62aSAndreas Boehler   * @param string $operationName The operation name
1444cb71a62aSAndreas Boehler   *
1445cb71a62aSAndreas Boehler   * @return mixed The operation code or false
1446cb71a62aSAndreas Boehler   */
144755a741c0SAndreas Boehler  public function operationNameToOperation($operationName)
144855a741c0SAndreas Boehler  {
144955a741c0SAndreas Boehler      switch($operationName)
145055a741c0SAndreas Boehler      {
145155a741c0SAndreas Boehler          case 'added':
145255a741c0SAndreas Boehler              return 1;
145355a741c0SAndreas Boehler          break;
145455a741c0SAndreas Boehler          case 'modified':
145555a741c0SAndreas Boehler              return 2;
145655a741c0SAndreas Boehler          break;
145755a741c0SAndreas Boehler          case 'deleted':
145855a741c0SAndreas Boehler              return 3;
145955a741c0SAndreas Boehler          break;
146055a741c0SAndreas Boehler      }
146155a741c0SAndreas Boehler      return false;
146255a741c0SAndreas Boehler  }
146355a741c0SAndreas Boehler
1464cb71a62aSAndreas Boehler  /**
1465cb71a62aSAndreas Boehler   * Update the sync token log based on the calendar id and the
1466cb71a62aSAndreas Boehler   * operation that was performed.
1467cb71a62aSAndreas Boehler   *
1468cb71a62aSAndreas Boehler   * @param string $calid The calendar ID that was modified
1469cb71a62aSAndreas Boehler   * @param string $uri The calendar URI that was modified
1470cb71a62aSAndreas Boehler   * @param string $operation The operation that was performed
1471cb71a62aSAndreas Boehler   *
1472cb71a62aSAndreas Boehler   * @return boolean True on success, otherwise false
1473cb71a62aSAndreas Boehler   */
147455a741c0SAndreas Boehler  private function updateSyncTokenLog($calid, $uri, $operation)
147555a741c0SAndreas Boehler  {
147655a741c0SAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
147755a741c0SAndreas Boehler      $operationCode = $this->operationNameToOperation($operation);
147855a741c0SAndreas Boehler      if(($operationCode === false) || ($currentToken === false))
147955a741c0SAndreas Boehler          return false;
148055a741c0SAndreas Boehler      $values = array($uri,
148155a741c0SAndreas Boehler                      $currentToken,
148255a741c0SAndreas Boehler                      $calid,
148355a741c0SAndreas Boehler                      $operationCode
148455a741c0SAndreas Boehler      );
14855f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
14865f2c3e2dSAndreas Boehler      if(!$sqlite)
14875f2c3e2dSAndreas Boehler        return false;
148851f4febbSAndreas Boehler      $query = "INSERT INTO calendarchanges (uri, synctoken, calendarid, operation) VALUES(?, ?, ?, ?)";
14895f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $uri, $currentToken, $calid, $operationCode);
149055a741c0SAndreas Boehler      if($res === false)
149155a741c0SAndreas Boehler        return false;
149255a741c0SAndreas Boehler      $currentToken++;
149351f4febbSAndreas Boehler      $query = "UPDATE calendars SET synctoken = ? WHERE id = ?";
14945f2c3e2dSAndreas Boehler      $res = $sqlite->query($query, $currentToken, $calid);
149555a741c0SAndreas Boehler      return ($res !== false);
149655a741c0SAndreas Boehler  }
149755a741c0SAndreas Boehler
1498cb71a62aSAndreas Boehler  /**
1499cb71a62aSAndreas Boehler   * Return the sync URL for a given Page, i.e. a calendar
1500cb71a62aSAndreas Boehler   *
1501cb71a62aSAndreas Boehler   * @param string $id The page's ID
1502cb71a62aSAndreas Boehler   * @param string $user (optional) The user's ID
1503cb71a62aSAndreas Boehler   *
1504cb71a62aSAndreas Boehler   * @return mixed The sync url or false
1505cb71a62aSAndreas Boehler   */
1506b269830cSAndreas Boehler  public function getSyncUrlForPage($id, $user = null)
1507b269830cSAndreas Boehler  {
1508415f5c8fSscottleechua      if(is_null($user))
150934a47953SAndreas Boehler      {
151034a47953SAndreas Boehler        if(isset($_SERVER['REMOTE_USER']) && !is_null($_SERVER['REMOTE_USER']))
151134a47953SAndreas Boehler        {
1512415f5c8fSscottleechua          $user = $_SERVER['REMOTE_USER'];
151334a47953SAndreas Boehler        }
151434a47953SAndreas Boehler        else
151534a47953SAndreas Boehler        {
151634a47953SAndreas Boehler          return false;
151734a47953SAndreas Boehler        }
151834a47953SAndreas Boehler      }
1519b269830cSAndreas Boehler
1520b269830cSAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1521b269830cSAndreas Boehler      if($calid === false)
1522b269830cSAndreas Boehler        return false;
1523b269830cSAndreas Boehler
1524b269830cSAndreas Boehler      $calsettings = $this->getCalendarSettings($calid);
1525b269830cSAndreas Boehler      if(!isset($calsettings['uri']))
1526b269830cSAndreas Boehler        return false;
1527b269830cSAndreas Boehler
1528b269830cSAndreas Boehler      $syncurl = DOKU_URL.'lib/plugins/davcal/calendarserver.php/calendars/'.$user.'/'.$calsettings['uri'];
1529b269830cSAndreas Boehler      return $syncurl;
1530b269830cSAndreas Boehler  }
1531b269830cSAndreas Boehler
1532cb71a62aSAndreas Boehler  /**
1533cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given page
1534cb71a62aSAndreas Boehler   *
1535cb71a62aSAndreas Boehler   * @param string $id the page ID
1536cb71a62aSAndreas Boehler   *
1537cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1538cb71a62aSAndreas Boehler   */
1539f69bb449SAndreas Boehler  public function getPrivateURLForPage($id)
1540f69bb449SAndreas Boehler  {
1541a8a4aa52Sscottleechua      // Check if this is an aggregated calendar (has multiple calendar pages)
1542a8a4aa52Sscottleechua      $calendarPages = $this->getCalendarPagesByMeta($id);
1543a8a4aa52Sscottleechua
1544a8a4aa52Sscottleechua      if($calendarPages !== false && count($calendarPages) > 1)
1545a8a4aa52Sscottleechua      {
1546a8a4aa52Sscottleechua          // This is an aggregated calendar - create a special private URL
1547a8a4aa52Sscottleechua          return $this->getPrivateURLForAggregatedCalendar($id, $calendarPages);
1548a8a4aa52Sscottleechua      }
1549a8a4aa52Sscottleechua
1550a8a4aa52Sscottleechua      // Single calendar - use the original logic
1551f69bb449SAndreas Boehler      $calid = $this->getCalendarIdForPage($id);
1552f69bb449SAndreas Boehler      if($calid === false)
1553f69bb449SAndreas Boehler        return false;
1554f69bb449SAndreas Boehler
1555f69bb449SAndreas Boehler      return $this->getPrivateURLForCalendar($calid);
1556f69bb449SAndreas Boehler  }
1557f69bb449SAndreas Boehler
1558cb71a62aSAndreas Boehler  /**
1559cb71a62aSAndreas Boehler   * Return the private calendar's URL for a given calendar ID
1560cb71a62aSAndreas Boehler   *
1561cb71a62aSAndreas Boehler   * @param string $calid The calendar's ID
1562cb71a62aSAndreas Boehler   *
1563cb71a62aSAndreas Boehler   * @return mixed The private URL or false
1564cb71a62aSAndreas Boehler   */
1565f69bb449SAndreas Boehler  public function getPrivateURLForCalendar($calid)
1566f69bb449SAndreas Boehler  {
1567185e2535SAndreas Boehler      if(isset($this->cachedValues['privateurl'][$calid]))
1568185e2535SAndreas Boehler        return $this->cachedValues['privateurl'][$calid];
15695f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
15705f2c3e2dSAndreas Boehler      if(!$sqlite)
15715f2c3e2dSAndreas Boehler        return false;
157251f4febbSAndreas Boehler      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
157361fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$calid]);
1574f69bb449SAndreas Boehler      if(!isset($row['url']))
1575f69bb449SAndreas Boehler      {
157602eea9b8SAndreas Boehler          $url = 'dokuwiki-' . bin2hex(random_bytes(16)) . '.ics';
157751f4febbSAndreas Boehler          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
157861fec75eSscottleechua          $sqlite->query($query, [$url, $calid]);
1579f69bb449SAndreas Boehler      }
1580f69bb449SAndreas Boehler      else
1581f69bb449SAndreas Boehler      {
1582f69bb449SAndreas Boehler          $url = $row['url'];
1583f69bb449SAndreas Boehler      }
1584185e2535SAndreas Boehler
1585185e2535SAndreas Boehler      $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
1586185e2535SAndreas Boehler      $this->cachedValues['privateurl'][$calid] = $url;
1587185e2535SAndreas Boehler      return $url;
1588f69bb449SAndreas Boehler  }
1589f69bb449SAndreas Boehler
1590cb71a62aSAndreas Boehler  /**
1591a8a4aa52Sscottleechua   * Return the private calendar's URL for an aggregated calendar
1592a8a4aa52Sscottleechua   *
1593a8a4aa52Sscottleechua   * @param string $id the page ID
1594a8a4aa52Sscottleechua   * @param array $calendarPages the calendar pages in the aggregation
1595a8a4aa52Sscottleechua   *
1596a8a4aa52Sscottleechua   * @return mixed The private URL or false
1597a8a4aa52Sscottleechua   */
1598a8a4aa52Sscottleechua  public function getPrivateURLForAggregatedCalendar($id, $calendarPages)
1599a8a4aa52Sscottleechua  {
1600a8a4aa52Sscottleechua      $aggregateId = 'aggregated-' . md5($id . serialize($calendarPages));
1601a8a4aa52Sscottleechua
1602a8a4aa52Sscottleechua      if(isset($this->cachedValues['privateurl'][$aggregateId]))
1603a8a4aa52Sscottleechua        return $this->cachedValues['privateurl'][$aggregateId];
1604a8a4aa52Sscottleechua
1605a8a4aa52Sscottleechua      $sqlite = $this->getDB();
1606a8a4aa52Sscottleechua      if(!$sqlite)
1607a8a4aa52Sscottleechua        return false;
1608a8a4aa52Sscottleechua
1609a8a4aa52Sscottleechua      $query = "SELECT url FROM calendartoprivateurlmapping WHERE calid = ?";
161061fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$aggregateId]);
1611a8a4aa52Sscottleechua
1612a8a4aa52Sscottleechua      if(!isset($row['url']))
1613a8a4aa52Sscottleechua      {
1614a8a4aa52Sscottleechua          $url = 'dokuwiki-aggregated-' . bin2hex(random_bytes(16)) . '.ics';
1615a8a4aa52Sscottleechua          $query = "INSERT INTO calendartoprivateurlmapping (url, calid) VALUES(?, ?)";
161661fec75eSscottleechua          $sqlite->query($query, [$url, $aggregateId]);
1617a8a4aa52Sscottleechua
1618a8a4aa52Sscottleechua          $query = "INSERT INTO pagetocalendarmapping (page, calid) VALUES(?, ?)";
161961fec75eSscottleechua          $sqlite->query($query, [$id, $aggregateId]);
1620a8a4aa52Sscottleechua      }
1621a8a4aa52Sscottleechua      else
1622a8a4aa52Sscottleechua      {
1623a8a4aa52Sscottleechua          $url = $row['url'];
1624a8a4aa52Sscottleechua      }
1625a8a4aa52Sscottleechua
1626a8a4aa52Sscottleechua      $url = DOKU_URL.'lib/plugins/davcal/ics.php/'.$url;
1627a8a4aa52Sscottleechua      $this->cachedValues['privateurl'][$aggregateId] = $url;
1628a8a4aa52Sscottleechua      return $url;
1629a8a4aa52Sscottleechua  }
1630a8a4aa52Sscottleechua
1631a8a4aa52Sscottleechua  /**
1632cb71a62aSAndreas Boehler   * Retrieve the calendar ID for a given private calendar URL
1633cb71a62aSAndreas Boehler   *
1634cb71a62aSAndreas Boehler   * @param string $url The private URL
1635cb71a62aSAndreas Boehler   *
1636cb71a62aSAndreas Boehler   * @return mixed The calendar ID or false
1637cb71a62aSAndreas Boehler   */
1638f69bb449SAndreas Boehler  public function getCalendarForPrivateURL($url)
1639f69bb449SAndreas Boehler  {
16405f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
16415f2c3e2dSAndreas Boehler      if(!$sqlite)
16425f2c3e2dSAndreas Boehler        return false;
164351f4febbSAndreas Boehler      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
164461fec75eSscottleechua      $row = $sqlite->queryRecord($query, [$url]);
1645f69bb449SAndreas Boehler      if(!isset($row['calid']))
1646f69bb449SAndreas Boehler        return false;
1647f69bb449SAndreas Boehler      return $row['calid'];
1648f69bb449SAndreas Boehler  }
1649f69bb449SAndreas Boehler
1650cb71a62aSAndreas Boehler  /**
1651cb71a62aSAndreas Boehler   * Return a given calendar as ICS feed, i.e. all events in one ICS file.
1652cb71a62aSAndreas Boehler   *
16537e0b8590SAndreas Boehler   * @param string $calid The calendar ID to retrieve
1654cb71a62aSAndreas Boehler   *
1655cb71a62aSAndreas Boehler   * @return mixed The calendar events as string or false
1656cb71a62aSAndreas Boehler   */
1657f69bb449SAndreas Boehler  public function getCalendarAsICSFeed($calid)
1658f69bb449SAndreas Boehler  {
1659f69bb449SAndreas Boehler      $calSettings = $this->getCalendarSettings($calid);
1660f69bb449SAndreas Boehler      if($calSettings === false)
1661f69bb449SAndreas Boehler        return false;
1662f69bb449SAndreas Boehler      $events = $this->getAllCalendarEvents($calid);
1663f69bb449SAndreas Boehler      if($events === false)
1664f69bb449SAndreas Boehler        return false;
1665f69bb449SAndreas Boehler
16667e0b8590SAndreas Boehler      // Load SabreDAV
16677e0b8590SAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
16687e0b8590SAndreas Boehler      $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
16697e0b8590SAndreas Boehler      $out .= $calSettings['displayname']."\r\n";
1670f69bb449SAndreas Boehler      foreach($events as $event)
1671f69bb449SAndreas Boehler      {
16727e0b8590SAndreas Boehler          $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
16737e0b8590SAndreas Boehler          $evt = $vcal->VEVENT;
16747e0b8590SAndreas Boehler          $out .= $evt->serialize();
1675f69bb449SAndreas Boehler      }
16767e0b8590SAndreas Boehler      $out .= "END:VCALENDAR\r\n";
1677f69bb449SAndreas Boehler      return $out;
1678f69bb449SAndreas Boehler  }
1679f69bb449SAndreas Boehler
16807c7c6b0bSAndreas Boehler  /**
1681a8a4aa52Sscottleechua   * Return an aggregated calendar as ICS feed, combining multiple calendars.
1682a8a4aa52Sscottleechua   *
1683a8a4aa52Sscottleechua   * @param string $icsFile The ICS file name for the aggregated calendar
1684a8a4aa52Sscottleechua   *
1685a8a4aa52Sscottleechua   * @return mixed The combined calendar events as string or false
1686a8a4aa52Sscottleechua   */
1687a8a4aa52Sscottleechua  public function getAggregatedCalendarAsICSFeed($icsFile)
1688a8a4aa52Sscottleechua  {
1689a8a4aa52Sscottleechua      $sqlite = $this->getDB();
1690a8a4aa52Sscottleechua      if(!$sqlite)
1691a8a4aa52Sscottleechua        return false;
1692a8a4aa52Sscottleechua
1693a8a4aa52Sscottleechua      // Find the aggregated calendar ID from the URL
1694a8a4aa52Sscottleechua      $query = "SELECT calid FROM calendartoprivateurlmapping WHERE url = ?";
16956a6f477eSscottleechua      $row = $sqlite->queryRecord($query, [$icsFile]);
1696a8a4aa52Sscottleechua
1697a8a4aa52Sscottleechua      if(!isset($row['calid']))
1698a8a4aa52Sscottleechua        return false;
1699a8a4aa52Sscottleechua
1700a8a4aa52Sscottleechua      $aggregateId = $row['calid'];
1701a8a4aa52Sscottleechua
1702a8a4aa52Sscottleechua      // Get the page ID for this aggregated calendar
1703a8a4aa52Sscottleechua      $query = "SELECT page FROM pagetocalendarmapping WHERE calid = ?";
17046a6f477eSscottleechua      $row = $sqlite->queryRecord($query, [$aggregateId]);
1705a8a4aa52Sscottleechua
1706a8a4aa52Sscottleechua      if(!isset($row['page']))
1707a8a4aa52Sscottleechua        return false;
1708a8a4aa52Sscottleechua
1709a8a4aa52Sscottleechua      $pageId = $row['page'];
1710a8a4aa52Sscottleechua
1711a8a4aa52Sscottleechua      // Get the calendar pages for this aggregated calendar
1712*ebcdb60aSscottleechua      $calendarPages = $this->getCalendarPagesByMeta($pageId, true); // Skip permission check for private URLs
1713a8a4aa52Sscottleechua      if($calendarPages === false || count($calendarPages) <= 1)
1714*ebcdb60aSscottleechua      {
1715*ebcdb60aSscottleechua          if($conf['allowdebug'])
1716*ebcdb60aSscottleechua              \dokuwiki\Logger::error('DAVCAL', 'No calendar pages found or count <= 1 for page: '.$pageId.' (count: '.(is_array($calendarPages) ? count($calendarPages) : 'false').')', __FILE__, __LINE__);
1717a8a4aa52Sscottleechua          return false;
1718*ebcdb60aSscottleechua      }
1719a8a4aa52Sscottleechua
1720a8a4aa52Sscottleechua      // Load SabreDAV
1721a8a4aa52Sscottleechua      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1722a8a4aa52Sscottleechua
1723a8a4aa52Sscottleechua      // Start building the combined ICS
1724a8a4aa52Sscottleechua      $out = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//DAVCal//DAVCal for DokuWiki//EN\r\nCALSCALE:GREGORIAN\r\nX-WR-CALNAME:";
1725a8a4aa52Sscottleechua      $out .= "Aggregated Calendar\r\n";
1726a8a4aa52Sscottleechua
1727a8a4aa52Sscottleechua      // Combine events from all calendars
1728a8a4aa52Sscottleechua      foreach($calendarPages as $calPage => $color)
1729a8a4aa52Sscottleechua      {
1730a8a4aa52Sscottleechua          $calid = $this->getCalendarIdForPage($calPage);
1731a8a4aa52Sscottleechua          if($calid === false)
1732a8a4aa52Sscottleechua            continue;
1733a8a4aa52Sscottleechua
1734a8a4aa52Sscottleechua          $events = $this->getAllCalendarEvents($calid);
1735a8a4aa52Sscottleechua          if($events === false)
1736a8a4aa52Sscottleechua            continue;
1737a8a4aa52Sscottleechua
1738a8a4aa52Sscottleechua          foreach($events as $event)
1739a8a4aa52Sscottleechua          {
1740a8a4aa52Sscottleechua              $vcal = \Sabre\VObject\Reader::read($event['calendardata']);
1741a8a4aa52Sscottleechua              $evt = $vcal->VEVENT;
1742a8a4aa52Sscottleechua              $out .= $evt->serialize();
1743a8a4aa52Sscottleechua          }
1744a8a4aa52Sscottleechua      }
1745a8a4aa52Sscottleechua
1746a8a4aa52Sscottleechua      $out .= "END:VCALENDAR\r\n";
1747a8a4aa52Sscottleechua      return $out;
1748a8a4aa52Sscottleechua  }
1749a8a4aa52Sscottleechua
1750a8a4aa52Sscottleechua  /**
17517c7c6b0bSAndreas Boehler   * Retrieve a configuration option for the plugin
17527c7c6b0bSAndreas Boehler   *
17537c7c6b0bSAndreas Boehler   * @param string $key The key to query
175421d04f73SAndreas Boehler   * @return mixed The option set, null if not found
17557c7c6b0bSAndreas Boehler   */
17567c7c6b0bSAndreas Boehler  public function getConfig($key)
17577c7c6b0bSAndreas Boehler  {
17587c7c6b0bSAndreas Boehler      return $this->getConf($key);
17597c7c6b0bSAndreas Boehler  }
17607c7c6b0bSAndreas Boehler
1761d5703f5aSAndreas Boehler  /**
1762d5703f5aSAndreas Boehler   * Parses some information from calendar objects, used for optimized
1763d5703f5aSAndreas Boehler   * calendar-queries. Taken nearly unmodified from Sabre's PDO backend
1764d5703f5aSAndreas Boehler   *
1765d5703f5aSAndreas Boehler   * Returns an array with the following keys:
1766d5703f5aSAndreas Boehler   *   * etag - An md5 checksum of the object without the quotes.
1767d5703f5aSAndreas Boehler   *   * size - Size of the object in bytes
1768d5703f5aSAndreas Boehler   *   * componentType - VEVENT, VTODO or VJOURNAL
1769d5703f5aSAndreas Boehler   *   * firstOccurence
1770d5703f5aSAndreas Boehler   *   * lastOccurence
1771d5703f5aSAndreas Boehler   *   * uid - value of the UID property
1772d5703f5aSAndreas Boehler   *
1773d5703f5aSAndreas Boehler   * @param string $calendarData
1774d5703f5aSAndreas Boehler   * @return array
1775d5703f5aSAndreas Boehler   */
1776d5703f5aSAndreas Boehler  protected function getDenormalizedData($calendarData)
1777d5703f5aSAndreas Boehler  {
1778d5703f5aSAndreas Boehler    require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1779d5703f5aSAndreas Boehler
1780d5703f5aSAndreas Boehler    $vObject = \Sabre\VObject\Reader::read($calendarData);
1781d5703f5aSAndreas Boehler    $componentType = null;
1782d5703f5aSAndreas Boehler    $component = null;
1783d5703f5aSAndreas Boehler    $firstOccurence = null;
1784d5703f5aSAndreas Boehler    $lastOccurence = null;
1785d5703f5aSAndreas Boehler    $uid = null;
1786d5703f5aSAndreas Boehler    foreach ($vObject->getComponents() as $component)
1787d5703f5aSAndreas Boehler    {
1788d5703f5aSAndreas Boehler        if ($component->name !== 'VTIMEZONE')
1789d5703f5aSAndreas Boehler        {
1790d5703f5aSAndreas Boehler            $componentType = $component->name;
1791d5703f5aSAndreas Boehler            $uid = (string)$component->UID;
1792d5703f5aSAndreas Boehler            break;
1793d5703f5aSAndreas Boehler        }
1794d5703f5aSAndreas Boehler    }
1795d5703f5aSAndreas Boehler    if (!$componentType)
1796d5703f5aSAndreas Boehler    {
1797d5703f5aSAndreas Boehler        return false;
1798d5703f5aSAndreas Boehler    }
1799d5703f5aSAndreas Boehler    if ($componentType === 'VEVENT')
1800d5703f5aSAndreas Boehler    {
1801d5703f5aSAndreas Boehler        $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp();
1802d5703f5aSAndreas Boehler        // Finding the last occurence is a bit harder
1803d5703f5aSAndreas Boehler        if (!isset($component->RRULE))
1804d5703f5aSAndreas Boehler        {
1805d5703f5aSAndreas Boehler            if (isset($component->DTEND))
1806d5703f5aSAndreas Boehler            {
1807d5703f5aSAndreas Boehler                $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp();
1808d5703f5aSAndreas Boehler            }
1809d5703f5aSAndreas Boehler            elseif (isset($component->DURATION))
1810d5703f5aSAndreas Boehler            {
1811d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1812d5703f5aSAndreas Boehler                $endDate->add(\Sabre\VObject\DateTimeParser::parse($component->DURATION->getValue()));
1813d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1814d5703f5aSAndreas Boehler            }
1815d5703f5aSAndreas Boehler            elseif (!$component->DTSTART->hasTime())
1816d5703f5aSAndreas Boehler            {
1817d5703f5aSAndreas Boehler                $endDate = clone $component->DTSTART->getDateTime();
1818d5703f5aSAndreas Boehler                $endDate->modify('+1 day');
1819d5703f5aSAndreas Boehler                $lastOccurence = $endDate->getTimeStamp();
1820d5703f5aSAndreas Boehler            }
1821d5703f5aSAndreas Boehler            else
1822d5703f5aSAndreas Boehler            {
1823d5703f5aSAndreas Boehler                $lastOccurence = $firstOccurence;
1824d5703f5aSAndreas Boehler            }
1825d5703f5aSAndreas Boehler        }
1826d5703f5aSAndreas Boehler        else
1827d5703f5aSAndreas Boehler        {
1828d5703f5aSAndreas Boehler            $it = new \Sabre\VObject\Recur\EventIterator($vObject, (string)$component->UID);
1829d5703f5aSAndreas Boehler            $maxDate = new \DateTime('2038-01-01');
1830d5703f5aSAndreas Boehler            if ($it->isInfinite())
1831d5703f5aSAndreas Boehler            {
1832d5703f5aSAndreas Boehler                $lastOccurence = $maxDate->getTimeStamp();
1833d5703f5aSAndreas Boehler            }
1834d5703f5aSAndreas Boehler            else
1835d5703f5aSAndreas Boehler            {
1836d5703f5aSAndreas Boehler                $end = $it->getDtEnd();
1837d5703f5aSAndreas Boehler                while ($it->valid() && $end < $maxDate)
1838d5703f5aSAndreas Boehler                {
1839d5703f5aSAndreas Boehler                    $end = $it->getDtEnd();
1840d5703f5aSAndreas Boehler                    $it->next();
1841d5703f5aSAndreas Boehler                }
1842d5703f5aSAndreas Boehler                $lastOccurence = $end->getTimeStamp();
1843d5703f5aSAndreas Boehler            }
1844d5703f5aSAndreas Boehler        }
1845d5703f5aSAndreas Boehler    }
1846d5703f5aSAndreas Boehler
1847d5703f5aSAndreas Boehler    return array(
1848d5703f5aSAndreas Boehler        'etag'           => md5($calendarData),
1849d5703f5aSAndreas Boehler        'size'           => strlen($calendarData),
1850d5703f5aSAndreas Boehler        'componentType'  => $componentType,
1851d5703f5aSAndreas Boehler        'firstOccurence' => $firstOccurence,
1852d5703f5aSAndreas Boehler        'lastOccurence'  => $lastOccurence,
1853d5703f5aSAndreas Boehler        'uid'            => $uid,
1854d5703f5aSAndreas Boehler    );
1855d5703f5aSAndreas Boehler
1856d5703f5aSAndreas Boehler  }
1857d5703f5aSAndreas Boehler
1858d5703f5aSAndreas Boehler  /**
1859d5703f5aSAndreas Boehler   * Query a calendar by ID and taking several filters into account.
1860d5703f5aSAndreas Boehler   * This is heavily based on Sabre's PDO backend.
1861d5703f5aSAndreas Boehler   *
1862d5703f5aSAndreas Boehler   * @param int $calendarId The calendar's ID
1863d5703f5aSAndreas Boehler   * @param array $filters The filter array to apply
1864d5703f5aSAndreas Boehler   *
1865d5703f5aSAndreas Boehler   * @return mixed The result
1866d5703f5aSAndreas Boehler   */
1867d5703f5aSAndreas Boehler  public function calendarQuery($calendarId, $filters)
1868d5703f5aSAndreas Boehler  {
1869c42afaebSscottleechua    \dokuwiki\Logger::debug('DAVCAL', 'Calendar query executed', __FILE__, __LINE__);
1870d5703f5aSAndreas Boehler    $componentType = null;
1871d5703f5aSAndreas Boehler    $requirePostFilter = true;
1872d5703f5aSAndreas Boehler    $timeRange = null;
18735f2c3e2dSAndreas Boehler    $sqlite = $this->getDB();
18745f2c3e2dSAndreas Boehler    if(!$sqlite)
18755f2c3e2dSAndreas Boehler      return false;
1876d5703f5aSAndreas Boehler
1877d5703f5aSAndreas Boehler    // if no filters were specified, we don't need to filter after a query
1878d5703f5aSAndreas Boehler    if (!$filters['prop-filters'] && !$filters['comp-filters'])
1879d5703f5aSAndreas Boehler    {
1880d5703f5aSAndreas Boehler        $requirePostFilter = false;
1881d5703f5aSAndreas Boehler    }
1882d5703f5aSAndreas Boehler
1883d5703f5aSAndreas Boehler    // Figuring out if there's a component filter
1884d5703f5aSAndreas Boehler    if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined'])
1885d5703f5aSAndreas Boehler    {
1886d5703f5aSAndreas Boehler        $componentType = $filters['comp-filters'][0]['name'];
1887d5703f5aSAndreas Boehler
1888d5703f5aSAndreas Boehler        // Checking if we need post-filters
1889d5703f5aSAndreas Boehler        if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters'])
1890d5703f5aSAndreas Boehler        {
1891d5703f5aSAndreas Boehler            $requirePostFilter = false;
1892d5703f5aSAndreas Boehler        }
1893d5703f5aSAndreas Boehler        // There was a time-range filter
1894d5703f5aSAndreas Boehler        if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range']))
1895d5703f5aSAndreas Boehler        {
1896d5703f5aSAndreas Boehler            $timeRange = $filters['comp-filters'][0]['time-range'];
1897d5703f5aSAndreas Boehler
1898d5703f5aSAndreas Boehler            // If start time OR the end time is not specified, we can do a
1899d5703f5aSAndreas Boehler            // 100% accurate mysql query.
1900d5703f5aSAndreas Boehler            if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end']))
1901d5703f5aSAndreas Boehler            {
1902d5703f5aSAndreas Boehler                $requirePostFilter = false;
1903d5703f5aSAndreas Boehler            }
1904d5703f5aSAndreas Boehler        }
1905d5703f5aSAndreas Boehler
1906d5703f5aSAndreas Boehler    }
1907d5703f5aSAndreas Boehler
1908d5703f5aSAndreas Boehler    if ($requirePostFilter)
1909d5703f5aSAndreas Boehler    {
1910d5703f5aSAndreas Boehler        $query = "SELECT uri, calendardata FROM calendarobjects WHERE calendarid = ?";
1911d5703f5aSAndreas Boehler    }
1912d5703f5aSAndreas Boehler    else
1913d5703f5aSAndreas Boehler    {
1914d5703f5aSAndreas Boehler        $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
1915d5703f5aSAndreas Boehler    }
1916d5703f5aSAndreas Boehler
1917d5703f5aSAndreas Boehler    $values = array(
1918d5703f5aSAndreas Boehler        $calendarId
1919d5703f5aSAndreas Boehler    );
1920d5703f5aSAndreas Boehler
1921d5703f5aSAndreas Boehler    if ($componentType)
1922d5703f5aSAndreas Boehler    {
1923d5703f5aSAndreas Boehler        $query .= " AND componenttype = ?";
1924d5703f5aSAndreas Boehler        $values[] = $componentType;
1925d5703f5aSAndreas Boehler    }
1926d5703f5aSAndreas Boehler
1927d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['start'])
1928d5703f5aSAndreas Boehler    {
1929d5703f5aSAndreas Boehler        $query .= " AND lastoccurence > ?";
1930d5703f5aSAndreas Boehler        $values[] = $timeRange['start']->getTimeStamp();
1931d5703f5aSAndreas Boehler    }
1932d5703f5aSAndreas Boehler    if ($timeRange && $timeRange['end'])
1933d5703f5aSAndreas Boehler    {
1934d5703f5aSAndreas Boehler        $query .= " AND firstoccurence < ?";
1935d5703f5aSAndreas Boehler        $values[] = $timeRange['end']->getTimeStamp();
1936d5703f5aSAndreas Boehler    }
1937d5703f5aSAndreas Boehler
19386a6f477eSscottleechua    $arr = $sqlite->queryAll($query, $values);
1939d5703f5aSAndreas Boehler
1940d5703f5aSAndreas Boehler    $result = array();
1941d5703f5aSAndreas Boehler    foreach($arr as $row)
1942d5703f5aSAndreas Boehler    {
1943d5703f5aSAndreas Boehler        if ($requirePostFilter)
1944d5703f5aSAndreas Boehler        {
1945d5703f5aSAndreas Boehler            if (!$this->validateFilterForObject($row, $filters))
1946d5703f5aSAndreas Boehler            {
1947d5703f5aSAndreas Boehler                continue;
1948d5703f5aSAndreas Boehler            }
1949d5703f5aSAndreas Boehler        }
1950d5703f5aSAndreas Boehler        $result[] = $row['uri'];
1951d5703f5aSAndreas Boehler
1952d5703f5aSAndreas Boehler    }
1953d5703f5aSAndreas Boehler
1954d5703f5aSAndreas Boehler    return $result;
1955d5703f5aSAndreas Boehler  }
1956d5703f5aSAndreas Boehler
1957d5703f5aSAndreas Boehler  /**
1958d5703f5aSAndreas Boehler   * This method validates if a filter (as passed to calendarQuery) matches
1959d5703f5aSAndreas Boehler   * the given object. Taken from Sabre's PDO backend
1960d5703f5aSAndreas Boehler   *
1961d5703f5aSAndreas Boehler   * @param array $object
1962d5703f5aSAndreas Boehler   * @param array $filters
1963d5703f5aSAndreas Boehler   * @return bool
1964d5703f5aSAndreas Boehler   */
1965d5703f5aSAndreas Boehler  protected function validateFilterForObject($object, $filters)
1966d5703f5aSAndreas Boehler  {
1967d5703f5aSAndreas Boehler      require_once(DOKU_PLUGIN.'davcal/vendor/autoload.php');
1968d5703f5aSAndreas Boehler      // Unfortunately, setting the 'calendardata' here is optional. If
1969d5703f5aSAndreas Boehler      // it was excluded, we actually need another call to get this as
1970d5703f5aSAndreas Boehler      // well.
1971d5703f5aSAndreas Boehler      if (!isset($object['calendardata']))
1972d5703f5aSAndreas Boehler      {
1973d5703f5aSAndreas Boehler          $object = $this->getCalendarObjectByUri($object['calendarid'], $object['uri']);
1974d5703f5aSAndreas Boehler      }
1975d5703f5aSAndreas Boehler
1976d5703f5aSAndreas Boehler      $vObject = \Sabre\VObject\Reader::read($object['calendardata']);
1977d5703f5aSAndreas Boehler      $validator = new \Sabre\CalDAV\CalendarQueryValidator();
1978d5703f5aSAndreas Boehler
197939787131SAndreas Boehler      $res = $validator->validate($vObject, $filters);
198039787131SAndreas Boehler      return $res;
1981d5703f5aSAndreas Boehler
1982d5703f5aSAndreas Boehler  }
1983d5703f5aSAndreas Boehler
1984d5703f5aSAndreas Boehler  /**
1985d5703f5aSAndreas Boehler   * Retrieve changes for a given calendar based on the given syncToken.
1986d5703f5aSAndreas Boehler   *
1987d5703f5aSAndreas Boehler   * @param int $calid The calendar's ID
1988d5703f5aSAndreas Boehler   * @param int $syncToken The supplied sync token
1989d5703f5aSAndreas Boehler   * @param int $syncLevel The sync level
1990d5703f5aSAndreas Boehler   * @param int $limit The limit of changes
1991d5703f5aSAndreas Boehler   *
1992d5703f5aSAndreas Boehler   * @return array The result
1993d5703f5aSAndreas Boehler   */
1994d5703f5aSAndreas Boehler  public function getChangesForCalendar($calid, $syncToken, $syncLevel, $limit = null)
1995d5703f5aSAndreas Boehler  {
1996d5703f5aSAndreas Boehler      // Current synctoken
1997d5703f5aSAndreas Boehler      $currentToken = $this->getSyncTokenForCalendar($calid);
1998d5703f5aSAndreas Boehler
1999d5703f5aSAndreas Boehler      if ($currentToken === false) return null;
2000d5703f5aSAndreas Boehler
2001d5703f5aSAndreas Boehler      $result = array(
2002d5703f5aSAndreas Boehler          'syncToken' => $currentToken,
2003d5703f5aSAndreas Boehler          'added'     => array(),
2004d5703f5aSAndreas Boehler          'modified'  => array(),
2005d5703f5aSAndreas Boehler          'deleted'   => array(),
2006d5703f5aSAndreas Boehler      );
20075f2c3e2dSAndreas Boehler      $sqlite = $this->getDB();
20085f2c3e2dSAndreas Boehler      if(!$sqlite)
20095f2c3e2dSAndreas Boehler        return false;
2010d5703f5aSAndreas Boehler
2011d5703f5aSAndreas Boehler      if ($syncToken)
2012d5703f5aSAndreas Boehler      {
2013d5703f5aSAndreas Boehler
2014d5703f5aSAndreas Boehler          $query = "SELECT uri, operation FROM calendarchanges WHERE synctoken >= ? AND synctoken < ? AND calendarid = ? ORDER BY synctoken";
2015d5703f5aSAndreas Boehler          if ($limit > 0) $query .= " LIMIT " . (int)$limit;
2016d5703f5aSAndreas Boehler
2017d5703f5aSAndreas Boehler          // Fetching all changes
20186a6f477eSscottleechua          $arr = $sqlite->queryAll($query, array($syncToken, $currentToken, $calid));
2019d5703f5aSAndreas Boehler          $changes = array();
2020d5703f5aSAndreas Boehler
2021d5703f5aSAndreas Boehler          // This loop ensures that any duplicates are overwritten, only the
2022d5703f5aSAndreas Boehler          // last change on a node is relevant.
2023d5703f5aSAndreas Boehler          foreach($arr as $row)
2024d5703f5aSAndreas Boehler          {
2025d5703f5aSAndreas Boehler              $changes[$row['uri']] = $row['operation'];
2026d5703f5aSAndreas Boehler          }
2027d5703f5aSAndreas Boehler
2028d5703f5aSAndreas Boehler          foreach ($changes as $uri => $operation)
2029d5703f5aSAndreas Boehler          {
2030d5703f5aSAndreas Boehler              switch ($operation)
2031d5703f5aSAndreas Boehler              {
2032d5703f5aSAndreas Boehler                  case 1 :
2033d5703f5aSAndreas Boehler                      $result['added'][] = $uri;
2034d5703f5aSAndreas Boehler                      break;
2035d5703f5aSAndreas Boehler                  case 2 :
2036d5703f5aSAndreas Boehler                      $result['modified'][] = $uri;
2037d5703f5aSAndreas Boehler                      break;
2038d5703f5aSAndreas Boehler                  case 3 :
2039d5703f5aSAndreas Boehler                      $result['deleted'][] = $uri;
2040d5703f5aSAndreas Boehler                      break;
2041d5703f5aSAndreas Boehler              }
2042d5703f5aSAndreas Boehler
2043d5703f5aSAndreas Boehler          }
2044d5703f5aSAndreas Boehler      }
2045d5703f5aSAndreas Boehler      else
2046d5703f5aSAndreas Boehler      {
2047d5703f5aSAndreas Boehler          // No synctoken supplied, this is the initial sync.
2048d5703f5aSAndreas Boehler          $query = "SELECT uri FROM calendarobjects WHERE calendarid = ?";
20496a6f477eSscottleechua          $arr = $sqlite->queryAll($query, array($calid));
2050d5703f5aSAndreas Boehler
2051d5703f5aSAndreas Boehler          $result['added'] = $arr;
2052d5703f5aSAndreas Boehler      }
2053d5703f5aSAndreas Boehler      return $result;
2054d5703f5aSAndreas Boehler  }
2055d5703f5aSAndreas Boehler
2056a1a3b679SAndreas Boehler}
2057