1<?php
2
3/**
4 * Kolab calendar storage class simulating a virtual calendar listing pedning/declined invitations
5 *
6 * @author Thomas Bruederli <bruederli@kolabsys.com>
7 *
8 * Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as
12 * published by the Free Software Foundation, either version 3 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24class kolab_invitation_calendar
25{
26  public $id = '__invitation__';
27  public $ready = true;
28  public $alarms = false;
29  public $rights = 'lrsv';
30  public $editable = false;
31  public $attachments = false;
32  public $subscriptions = false;
33  public $partstats = array('unknown');
34  public $categories = array();
35  public $name = 'Invitations';
36
37
38  /**
39   * Default constructor
40   */
41  public function __construct($id, $calendar)
42  {
43    $this->cal = $calendar;
44    $this->id = $id;
45
46    switch ($this->id) {
47      case kolab_driver::INVITATIONS_CALENDAR_PENDING:
48        $this->partstats = array('NEEDS-ACTION');
49        $this->name = $this->cal->gettext('invitationspending');
50        if (!empty($_REQUEST['_quickview']))
51          $this->partstats[] = 'TENTATIVE';
52        break;
53
54      case kolab_driver::INVITATIONS_CALENDAR_DECLINED:
55        $this->partstats = array('DECLINED');
56        $this->name = $this->cal->gettext('invitationsdeclined');
57        break;
58    }
59
60    // user-specific alarms settings win
61    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
62    if (isset($prefs[$this->id]['showalarms']))
63      $this->alarms = $prefs[$this->id]['showalarms'];
64  }
65
66  /**
67   * Getter for a nice and human readable name for this calendar
68   *
69   * @return string Name of this calendar
70   */
71  public function get_name()
72  {
73    return $this->name;
74  }
75
76  /**
77   * Getter for the IMAP folder owner
78   *
79   * @return string Name of the folder owner
80   */
81  public function get_owner()
82  {
83    return $this->cal->rc->get_user_name();
84  }
85
86  /**
87   *
88   */
89  public function get_title()
90  {
91    return $this->get_name();
92  }
93
94  /**
95   * Getter for the name of the namespace to which the IMAP folder belongs
96   *
97   * @return string Name of the namespace (personal, other, shared)
98   */
99  public function get_namespace()
100  {
101    return 'x-special';
102  }
103
104  /**
105   * Getter for the top-end calendar folder name (not the entire path)
106   *
107   * @return string Name of this calendar
108   */
109  public function get_foldername()
110  {
111    return $this->get_name();
112  }
113
114  /**
115   * Getter for the Cyrus mailbox identifier corresponding to this folder
116   *
117   * @return string Mailbox ID
118   */
119  public function get_mailbox_id()
120  {
121    // this is a virtual collection and has no concrete mailbox ID
122    return null;
123  }
124
125  /**
126   * Return color to display this calendar
127   */
128  public function get_color()
129  {
130    // calendar color is stored in local user prefs
131    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
132
133    if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
134      return $prefs[$this->id]['color'];
135
136    return 'ffffff';
137  }
138
139  /**
140   * Compose an URL for CalDAV access to this calendar (if configured)
141   */
142  public function get_caldav_url()
143  {
144    return false;
145  }
146
147  /**
148   * Check activation status of this folder
149   *
150   * @return boolean True if enabled, false if not
151   */
152  public function is_active()
153  {
154    $prefs = $this->cal->rc->config->get('kolab_calendars', array());  // read local prefs
155    return (bool)$prefs[$this->id]['active'];
156  }
157
158  /**
159   * Update properties of this calendar folder
160   *
161   * @see calendar_driver::edit_calendar()
162   */
163  public function update(&$prop)
164  {
165    // don't change anything.
166    // let kolab_driver save props in local prefs
167    return $prop['id'];
168  }
169
170  /**
171   * Getter for a single event object
172   */
173  public function get_event($id)
174  {
175    // redirect call to kolab_driver::get_event()
176    $event = $this->cal->driver->get_event($id, calendar_driver::FILTER_WRITEABLE);
177
178    if (is_array($event)) {
179      $event = $this->_mod_event($event, $event['calendar']);
180    }
181
182    return $event;
183  }
184
185  /**
186   * Create instances of a recurring event
187   *
188   * @see kolab_calendar::get_recurring_events()
189   */
190  public function get_recurring_events($event, $start, $end = null, $event_id = null, $limit = null)
191  {
192    // forward call to the actual storage folder
193    if ($event['_folder_id']) {
194      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
195      if ($cal && $cal->ready) {
196        return $cal->get_recurring_events($event, $start, $end, $event_id, $limit);
197      }
198    }
199  }
200
201  /**
202   * Get attachment body
203   *
204   * @see calendar_driver::get_attachment_body()
205   */
206  public function get_attachment_body($id, $event)
207  {
208    // find the actual folder this event resides in
209    if (!empty($event['_folder_id'])) {
210      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
211    }
212    else {
213      $cal = null;
214      foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
215        $cal = $this->_get_calendar($foldername);
216        if ($cal->ready && $cal->storage && $cal->get_event($event['id'])) {
217          break;
218        }
219      }
220    }
221
222    if ($cal && $cal->storage) {
223      return $cal->get_attachment_body($id, $event);
224    }
225
226    return false;
227  }
228
229  /**
230   * @param integer Event's new start (unix timestamp)
231   * @param integer Event's new end (unix timestamp)
232   * @param string  Search query (optional)
233   * @param boolean Include virtual events (optional)
234   * @param array   Additional parameters to query storage
235   *
236   * @return array A list of event records
237   */
238  public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
239  {
240    // get email addresses of the current user
241    $user_emails = $this->cal->get_user_emails();
242    $subquery = array();
243    foreach ($user_emails as $email) {
244      foreach ($this->partstats as $partstat) {
245        $subquery[] = array('tags', '=', 'x-partstat:' . $email . ':' . strtolower($partstat));
246      }
247    }
248
249    // aggregate events from all calendar folders
250    $events = array();
251    foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
252      $cal = $this->_get_calendar($foldername);
253      if (!$cal || $cal->get_namespace() == 'other')
254        continue;
255
256      foreach ($cal->list_events($start, $end, $search, 1, $query, array(array($subquery, 'OR'))) as $event) {
257        $match = false;
258
259        // post-filter events to match out partstats
260        if (is_array($event['attendees'])) {
261          foreach ($event['attendees'] as $attendee) {
262            if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $this->partstats)) {
263              $match = true;
264              break;
265            }
266          }
267        }
268
269        if ($match) {
270          $events[$event['id'] ?: $event['uid']] = $this->_mod_event($event, $cal->id);
271        }
272      }
273
274      // merge list of event categories (really?)
275      $this->categories += $cal->categories;
276    }
277
278    return $events;
279  }
280
281  /**
282   * Get number of events in the given calendar
283   *
284   * @param integer Date range start (unix timestamp)
285   * @param integer Date range end (unix timestamp)
286   * @param array   Additional query to filter events
287   *
288   * @return integer Count
289   */
290  public function count_events($start, $end = null, $filter = null)
291  {
292    // get email addresses of the current user
293    $user_emails = $this->cal->get_user_emails();
294    $subquery = array();
295    foreach ($user_emails as $email) {
296      foreach ($this->partstats as $partstat) {
297        $subquery[] = array('tags', '=', 'x-partstat:' . $email . ':' . strtolower($partstat));
298      }
299    }
300
301    $filter = array(
302      array('tags','!=','x-status:cancelled'),
303      array($subquery, 'OR')
304    );
305
306    // aggregate counts from all calendar folders
307    $count = 0;
308    foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
309      $cal = $this->_get_calendar($foldername);
310      if (!$cal || $cal->get_namespace() == 'other')
311        continue;
312
313      $count += $cal->count_events($start, $end, $filter);
314    }
315
316    return $count;
317  }
318
319  /**
320   * Get calendar object instance (that maybe already initialized)
321   */
322  private function _get_calendar($folder_name)
323  {
324    $id = kolab_storage::folder_id($folder_name, true);
325    return $this->cal->driver->get_calendar($id);
326  }
327
328  /**
329   * Helper method to modify some event properties
330   */
331  private function _mod_event($event, $calendar_id = null)
332  {
333    // set classes according to PARTSTAT
334    $event = kolab_driver::add_partstat_class($event, $this->partstats);
335
336    if (strpos($event['className'], 'fc-invitation-') !== false) {
337      $event['calendar'] = $this->id;
338    }
339
340    // add pointer to original calendar folder
341    if ($calendar_id) {
342      $event['_folder_id'] = $calendar_id;
343    }
344
345    return $event;
346  }
347
348  /**
349   * Create a new event record
350   *
351   * @see kolab_calendar::insert_event()
352   */
353  public function insert_event($event)
354  {
355    return false;
356  }
357
358  /**
359   * Update a specific event record
360   *
361   * @see kolab_calendar::update_event()
362   */
363  public function update_event($event, $exception_id = null)
364  {
365    // forward call to the actual storage folder
366    if ($event['_folder_id']) {
367      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
368      if ($cal && $cal->ready) {
369        return $cal->update_event($event, $exception_id);
370      }
371    }
372
373    return false;
374  }
375
376  /**
377   * Delete an event record
378   *
379   * @see kolab_calendar::delete_event()
380   */
381  public function delete_event($event, $force = true)
382  {
383    // forward call to the actual storage folder
384    if ($event['_folder_id']) {
385      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
386      if ($cal && $cal->ready) {
387        return $cal->delete_event($event, $force);
388      }
389    }
390
391    return false;
392  }
393
394  /**
395   * Restore deleted event record
396   *
397   * @see kolab_calendar::restore_event()
398   */
399  public function restore_event($event)
400  {
401    // forward call to the actual storage folder
402    if ($event['_folder_id']) {
403      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
404      if ($cal && $cal->ready) {
405        return $cal->restore_event($event);
406      }
407    }
408
409    return false;
410  }
411}
412