Changeset View
Changeset View
Standalone View
Standalone View
plugins/calendar/drivers/kolab/kolab_user_calendar.php
Show All 17 Lines | |||||
* GNU Affero General Public License for more details. | * GNU Affero General Public License for more details. | ||||
* | * | ||||
* You should have received a copy of the GNU Affero General Public License | * You should have received a copy of the GNU Affero General Public License | ||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
*/ | */ | ||||
class kolab_user_calendar extends kolab_calendar | class kolab_user_calendar extends kolab_calendar | ||||
{ | { | ||||
public $id = 'unknown'; | public $id = 'unknown'; | ||||
public $ready = false; | public $ready = false; | ||||
public $editable = false; | public $editable = false; | ||||
public $attachments = false; | public $attachments = false; | ||||
public $subscriptions = false; | public $subscriptions = false; | ||||
protected $userdata = array(); | protected $userdata = []; | ||||
protected $timeindex = array(); | protected $timeindex = []; | ||||
/** | /** | ||||
* Default constructor | * Default constructor | ||||
*/ | */ | ||||
public function __construct($user_or_folder, $calendar) | public function __construct($user_or_folder, $calendar) | ||||
{ | { | ||||
$this->cal = $calendar; | $this->cal = $calendar; | ||||
$this->imap = $calendar->rc->get_storage(); | $this->imap = $calendar->rc->get_storage(); | ||||
// full user record is provided | // full user record is provided | ||||
if (is_array($user_or_folder)) { | if (is_array($user_or_folder)) { | ||||
$this->userdata = $user_or_folder; | $this->userdata = $user_or_folder; | ||||
$this->storage = new kolab_storage_folder_user($this->userdata['kolabtargetfolder'], '', $this->userdata); | $this->storage = new kolab_storage_folder_user($this->userdata['kolabtargetfolder'], '', $this->userdata); | ||||
} | } | ||||
else if ($user_or_folder instanceof kolab_storage_folder_user) { | else if ($user_or_folder instanceof kolab_storage_folder_user) { | ||||
$this->storage = $user_or_folder; | $this->storage = $user_or_folder; | ||||
$this->userdata = $this->storage->ldaprec; | $this->userdata = $this->storage->ldaprec; | ||||
} | } | ||||
else { // get user record from LDAP | else { | ||||
// get user record from LDAP | |||||
$this->storage = new kolab_storage_folder_user($user_or_folder); | $this->storage = new kolab_storage_folder_user($user_or_folder); | ||||
$this->userdata = $this->storage->ldaprec; | $this->userdata = $this->storage->ldaprec; | ||||
} | } | ||||
$this->ready = !empty($this->userdata['kolabtargetfolder']); | $this->ready = !empty($this->userdata['kolabtargetfolder']); | ||||
$this->storage->type = 'event'; | $this->storage->type = 'event'; | ||||
if ($this->ready) { | if ($this->ready) { | ||||
// ID is derrived from the user's kolabtargetfolder attribute | // ID is derrived from the user's kolabtargetfolder attribute | ||||
$this->id = kolab_storage::folder_id($this->userdata['kolabtargetfolder'], true); | $this->id = kolab_storage::folder_id($this->userdata['kolabtargetfolder'], true); | ||||
$this->imap_folder = $this->userdata['kolabtargetfolder']; | $this->imap_folder = $this->userdata['kolabtargetfolder']; | ||||
$this->name = $this->storage->name; | $this->name = $this->storage->name; | ||||
$this->parent = ''; // user calendars are top level | $this->parent = ''; // user calendars are top level | ||||
// user-specific alarms settings win | // user-specific alarms settings win | ||||
$prefs = $this->cal->rc->config->get('kolab_calendars', array()); | $prefs = $this->cal->rc->config->get('kolab_calendars', []); | ||||
if (isset($prefs[$this->id]['showalarms'])) | if (isset($prefs[$this->id]['showalarms'])) { | ||||
$this->alarms = $prefs[$this->id]['showalarms']; | $this->alarms = $prefs[$this->id]['showalarms']; | ||||
} | } | ||||
} | } | ||||
} | |||||
/** | /** | ||||
* Getter for a nice and human readable name for this calendar | * Getter for a nice and human readable name for this calendar | ||||
* | * | ||||
* @return string Name of this calendar | * @return string Name of this calendar | ||||
*/ | */ | ||||
public function get_name() | public function get_name() | ||||
{ | { | ||||
return $this->userdata['displayname'] ?: ($this->userdata['name'] ?: $this->userdata['mail']); | if (!empty($this->userdata['displayname'])) { | ||||
return $this->userdata['displayname']; | |||||
} | |||||
return !empty($this->userdata['name']) ? $this->userdata['name'] : $this->userdata['mail']; | |||||
} | } | ||||
/** | /** | ||||
* Getter for the IMAP folder owner | * Getter for the IMAP folder owner | ||||
* | * | ||||
* @param bool Return a fully qualified owner name (unused) | * @param bool Return a fully qualified owner name (unused) | ||||
* | * | ||||
* @return string Name of the folder owner | * @return string Name of the folder owner | ||||
*/ | */ | ||||
public function get_owner($fully_qualified = false) | public function get_owner($fully_qualified = false) | ||||
{ | { | ||||
return $this->userdata['mail']; | return $this->userdata['mail']; | ||||
} | } | ||||
/** | /** | ||||
* | * | ||||
*/ | */ | ||||
public function get_title() | public function get_title() | ||||
{ | { | ||||
return trim($this->userdata['displayname'] . '; ' . $this->userdata['mail'], '; '); | $title = []; | ||||
if (!empty($this->userdata['displayname'])) { | |||||
$title[] = $this->userdata['displayname']; | |||||
} | |||||
$title[] = $this->userdata['mail']; | |||||
return implode('; ', $title); | |||||
} | } | ||||
/** | /** | ||||
* Getter for the name of the namespace to which the IMAP folder belongs | * Getter for the name of the namespace to which the IMAP folder belongs | ||||
* | * | ||||
* @return string Name of the namespace (personal, other, shared) | * @return string Name of the namespace (personal, other, shared) | ||||
*/ | */ | ||||
public function get_namespace() | public function get_namespace() | ||||
{ | { | ||||
return 'other user'; | return 'other user'; | ||||
} | } | ||||
/** | /** | ||||
* Getter for the top-end calendar folder name (not the entire path) | * Getter for the top-end calendar folder name (not the entire path) | ||||
* | * | ||||
* @return string Name of this calendar | * @return string Name of this calendar | ||||
*/ | */ | ||||
public function get_foldername() | public function get_foldername() | ||||
{ | { | ||||
return $this->get_name(); | return $this->get_name(); | ||||
} | } | ||||
/** | /** | ||||
* Return color to display this calendar | * Return color to display this calendar | ||||
*/ | */ | ||||
public function get_color($default = null) | public function get_color($default = null) | ||||
{ | { | ||||
// calendar color is stored in local user prefs | // calendar color is stored in local user prefs | ||||
$prefs = $this->cal->rc->config->get('kolab_calendars', array()); | $prefs = $this->cal->rc->config->get('kolab_calendars', []); | ||||
if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color'])) | if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color'])) { | ||||
return $prefs[$this->id]['color']; | return $prefs[$this->id]['color']; | ||||
} | |||||
return $default ?: 'cc0000'; | return $default ?: 'cc0000'; | ||||
} | } | ||||
/** | /** | ||||
* Compose an URL for CalDAV access to this calendar (if configured) | * Compose an URL for CalDAV access to this calendar (if configured) | ||||
*/ | */ | ||||
public function get_caldav_url() | public function get_caldav_url() | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Check subscription status of this folder | * Check subscription status of this folder | ||||
* | * | ||||
* @return boolean True if subscribed, false if not | * @return boolean True if subscribed, false if not | ||||
*/ | */ | ||||
public function is_subscribed() | public function is_subscribed() | ||||
{ | { | ||||
return $this->storage->is_subscribed(); | return $this->storage->is_subscribed(); | ||||
} | } | ||||
/** | /** | ||||
* Update properties of this calendar folder | * Update properties of this calendar folder | ||||
* | * | ||||
* @see calendar_driver::edit_calendar() | * @see calendar_driver::edit_calendar() | ||||
*/ | */ | ||||
public function update(&$prop) | public function update(&$prop) | ||||
{ | { | ||||
// don't change anything. | // don't change anything. | ||||
// let kolab_driver save props in local prefs | // let kolab_driver save props in local prefs | ||||
return $prop['id']; | return $prop['id']; | ||||
} | } | ||||
/** | /** | ||||
* Getter for a single event object | * Getter for a single event object | ||||
*/ | */ | ||||
public function get_event($id) | public function get_event($id) | ||||
{ | { | ||||
// TODO: implement this | // TODO: implement this | ||||
return $this->events[$id]; | return isset($this->events[$id]) ? $this->events[$id] : null; | ||||
} | } | ||||
/** | /** | ||||
* Get attachment body | * Get attachment body | ||||
* @see calendar_driver::get_attachment_body() | * @see calendar_driver::get_attachment_body() | ||||
*/ | */ | ||||
public function get_attachment_body($id, $event) | public function get_attachment_body($id, $event) | ||||
{ | { | ||||
if (!$event['calendar'] && ($ev = $this->get_event($event['id']))) { | if (empty($event['calendar']) && ($ev = $this->get_event($event['id']))) { | ||||
$event['calendar'] = $ev['calendar']; | $event['calendar'] = $ev['calendar']; | ||||
} | } | ||||
if ($event['calendar'] && ($cal = $this->cal->get_calendar($event['calendar']))) { | if (!empty($event['calendar']) && ($cal = $this->cal->get_calendar($event['calendar']))) { | ||||
return $cal->get_attachment_body($id, $event); | return $cal->get_attachment_body($id, $event); | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* @param integer Event's new start (unix timestamp) | * @param int Event's new start (unix timestamp) | ||||
* @param integer Event's new end (unix timestamp) | * @param int Event's new end (unix timestamp) | ||||
* @param string Search query (optional) | * @param string Search query (optional) | ||||
* @param boolean Include virtual events (optional) | * @param bool Include virtual events (optional) | ||||
* @param array Additional parameters to query storage | * @param array Additional parameters to query storage | ||||
* @param array Additional query to filter events | * @param array Additional query to filter events | ||||
* | * | ||||
* @return array A list of event records | * @return array A list of event records | ||||
*/ | */ | ||||
public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null) | public function list_events($start, $end, $search = null, $virtual = 1, $query = [], $filter_query = null) | ||||
{ | { | ||||
// convert to DateTime for comparisons | // convert to DateTime for comparisons | ||||
try { | try { | ||||
$start_dt = new DateTime('@'.$start); | $start_dt = new DateTime('@'.$start); | ||||
} | } | ||||
catch (Exception $e) { | catch (Exception $e) { | ||||
$start_dt = new DateTime('@0'); | $start_dt = new DateTime('@0'); | ||||
} | } | ||||
try { | try { | ||||
$end_dt = new DateTime('@'.$end); | $end_dt = new DateTime('@'.$end); | ||||
} | } | ||||
catch (Exception $e) { | catch (Exception $e) { | ||||
$end_dt = new DateTime('today +10 years'); | $end_dt = new DateTime('today +10 years'); | ||||
} | } | ||||
$limit_changed = null; | $limit_changed = null; | ||||
if (!empty($query)) { | if (!empty($query)) { | ||||
foreach ($query as $q) { | foreach ($query as $q) { | ||||
if ($q[0] == 'changed' && $q[1] == '>=') { | if ($q[0] == 'changed' && $q[1] == '>=') { | ||||
try { $limit_changed = new DateTime('@'.$q[2]); } | try { $limit_changed = new DateTime('@'.$q[2]); } | ||||
catch (Exception $e) { /* ignore */ } | catch (Exception $e) { /* ignore */ } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// aggregate all calendar folders the user shares (but are not activated) | // aggregate all calendar folders the user shares (but are not activated) | ||||
foreach (kolab_storage::list_user_folders($this->userdata, 'event', 2) as $foldername) { | foreach (kolab_storage::list_user_folders($this->userdata, 'event', 2) as $foldername) { | ||||
$cal = new kolab_calendar($foldername, $this->cal); | $cal = new kolab_calendar($foldername, $this->cal); | ||||
foreach ($cal->list_events($start, $end, $search, 1) as $event) { | foreach ($cal->list_events($start, $end, $search, 1) as $event) { | ||||
$uid = $event['id'] ?: $event['uid']; | $uid = !empty($event['id']) ? $event['id'] : $event['uid']; | ||||
$this->events[$uid] = $event; | $this->events[$uid] = $event; | ||||
$this->timeindex[$this->time_key($event)] = $uid; | $this->timeindex[$this->time_key($event)] = $uid; | ||||
} | } | ||||
} | } | ||||
// get events from the user's free/busy feed (for quickview only) | // get events from the user's free/busy feed (for quickview only) | ||||
$fbview = $this->cal->rc->config->get('calendar_include_freebusy_data', 1); | $fbview = $this->cal->rc->config->get('calendar_include_freebusy_data', 1); | ||||
if ($fbview && ($fbview == 1 || !empty($_REQUEST['_quickview'])) && empty($search)) { | if ($fbview && ($fbview == 1 || !empty($_REQUEST['_quickview'])) && empty($search)) { | ||||
$this->fetch_freebusy($limit_changed); | $this->fetch_freebusy($limit_changed); | ||||
} | } | ||||
$events = array(); | $events = []; | ||||
foreach ($this->events as $event) { | foreach ($this->events as $event) { | ||||
// list events in requested time window | // list events in requested time window | ||||
if ($event['start'] <= $end_dt && $event['end'] >= $start_dt && | if ( | ||||
(!$limit_changed || !$event['changed'] || $event['changed'] >= $limit_changed)) { | $event['start'] <= $end_dt | ||||
&& $event['end'] >= $start_dt | |||||
&& (!$limit_changed || empty($event['changed']) || $event['changed'] >= $limit_changed) | |||||
) { | |||||
$events[] = $event; | $events[] = $event; | ||||
} | } | ||||
} | } | ||||
// avoid session race conditions that will loose temporary subscriptions | // avoid session race conditions that will loose temporary subscriptions | ||||
$this->cal->rc->session->nowrite = true; | $this->cal->rc->session->nowrite = true; | ||||
return $events; | return $events; | ||||
} | } | ||||
/** | /** | ||||
* Get number of events in the given calendar | * Get number of events in the given calendar | ||||
* | * | ||||
* @param integer Date range start (unix timestamp) | * @param int Date range start (unix timestamp) | ||||
* @param integer Date range end (unix timestamp) | * @param int Date range end (unix timestamp) | ||||
* @param array Additional query to filter events | * @param array Additional query to filter events | ||||
* | * | ||||
* @return integer Count | * @return integer Count | ||||
*/ | */ | ||||
public function count_events($start, $end = null, $filter_query = null) | public function count_events($start, $end = null, $filter_query = null) | ||||
{ | { | ||||
// not implemented | // not implemented | ||||
return 0; | return 0; | ||||
} | } | ||||
/** | /** | ||||
* Helper method to fetch free/busy data for the user and turn it into calendar data | * Helper method to fetch free/busy data for the user and turn it into calendar data | ||||
*/ | */ | ||||
private function fetch_freebusy($limit_changed = null) | private function fetch_freebusy($limit_changed = null) | ||||
{ | { | ||||
// ask kolab server first | // ask kolab server first | ||||
try { | try { | ||||
$request_config = array( | $request_config = [ | ||||
'store_body' => true, | 'store_body' => true, | ||||
'follow_redirects' => true, | 'follow_redirects' => true, | ||||
); | ]; | ||||
$request = libkolab::http_request(kolab_storage::get_freebusy_url($this->userdata['mail']), 'GET', $request_config); | $request = libkolab::http_request(kolab_storage::get_freebusy_url($this->userdata['mail']), 'GET', $request_config); | ||||
$response = $request->send(); | $response = $request->send(); | ||||
// authentication required | // authentication required | ||||
if ($response->getStatus() == 401) { | if ($response->getStatus() == 401) { | ||||
$request->setAuth($this->cal->rc->user->get_username(), $this->cal->rc->decrypt($_SESSION['password'])); | $request->setAuth($this->cal->rc->user->get_username(), $this->cal->rc->decrypt($_SESSION['password'])); | ||||
$response = $request->send(); | $response = $request->send(); | ||||
} | } | ||||
if ($response->getStatus() == 200) | if ($response->getStatus() == 200) { | ||||
$fbdata = $response->getBody(); | $fbdata = $response->getBody(); | ||||
} | |||||
unset($request, $response); | unset($request, $response); | ||||
} | } | ||||
catch (Exception $e) { | catch (Exception $e) { | ||||
rcube::raise_error(array( | rcube::raise_error([ | ||||
'code' => 900, | 'code' => 900, 'file' => __FILE__, 'line' => __LINE__, | ||||
'type' => 'php', | 'message' => "Error fetching free/busy information: " . $e->getMessage() | ||||
'file' => __FILE__, | ], | ||||
'line' => __LINE__, | true, false | ||||
'message' => "Error fetching free/busy information: " . $e->getMessage()), | ); | ||||
true, false); | |||||
return false; | return false; | ||||
} | } | ||||
$statusmap = array( | $statusmap = [ | ||||
'FREE' => 'free', | 'FREE' => 'free', | ||||
'BUSY' => 'busy', | 'BUSY' => 'busy', | ||||
'BUSY-TENTATIVE' => 'tentative', | 'BUSY-TENTATIVE' => 'tentative', | ||||
'X-OUT-OF-OFFICE' => 'outofoffice', | 'X-OUT-OF-OFFICE' => 'outofoffice', | ||||
'OOF' => 'outofoffice', | 'OOF' => 'outofoffice', | ||||
); | ]; | ||||
$titlemap = array( | |||||
$titlemap = [ | |||||
'FREE' => $this->cal->gettext('availfree'), | 'FREE' => $this->cal->gettext('availfree'), | ||||
'BUSY' => $this->cal->gettext('availbusy'), | 'BUSY' => $this->cal->gettext('availbusy'), | ||||
'BUSY-TENTATIVE' => $this->cal->gettext('availtentative'), | 'BUSY-TENTATIVE' => $this->cal->gettext('availtentative'), | ||||
'X-OUT-OF-OFFICE' => $this->cal->gettext('availoutofoffice'), | 'X-OUT-OF-OFFICE' => $this->cal->gettext('availoutofoffice'), | ||||
); | ]; | ||||
// rcube::console('_fetch_freebusy', kolab_storage::get_freebusy_url($this->userdata['mail']), $fbdata); | // rcube::console('_fetch_freebusy', kolab_storage::get_freebusy_url($this->userdata['mail']), $fbdata); | ||||
// parse free-busy information | |||||
$count = 0; | $count = 0; | ||||
if ($fbdata) { | |||||
// parse free-busy information | |||||
if (!empty($fbdata)) { | |||||
$ical = $this->cal->get_ical(); | $ical = $this->cal->get_ical(); | ||||
$ical->import($fbdata); | $ical->import($fbdata); | ||||
if ($fb = $ical->freebusy) { | if ($fb = $ical->freebusy) { | ||||
// consider 'changed >= X' queries | // consider 'changed >= X' queries | ||||
if ($limit_changed && $fb['created'] && $fb['created'] < $limit_changed) { | if ($limit_changed && !empty($fb['created']) && $fb['created'] < $limit_changed) { | ||||
return 0; | return 0; | ||||
} | } | ||||
foreach ($fb['periods'] as $tuple) { | foreach ($fb['periods'] as $tuple) { | ||||
list($from, $to, $type) = $tuple; | list($from, $to, $type) = $tuple; | ||||
$event = array( | $event = [ | ||||
'uid' => md5($this->id . $from->format('U') . '/' . $to->format('U')), | 'uid' => md5($this->id . $from->format('U') . '/' . $to->format('U')), | ||||
'calendar' => $this->id, | 'calendar' => $this->id, | ||||
'changed' => $fb['created'] ?: new DateTime(), | 'changed' => !empty($fb['created']) ? $fb['created'] : new DateTime(), | ||||
'title' => $this->get_name() . ' ' . ($titlemap[$type] ?: $type), | 'title' => $this->get_name() . ' ' . (!empty($titlemap[$type]) ? $titlemap[$type] : $type), | ||||
'start' => $from, | 'start' => $from, | ||||
'end' => $to, | 'end' => $to, | ||||
'free_busy' => $statusmap[$type] ?: 'busy', | 'free_busy' => !empty($statusmap[$type]) ? $statusmap[$type] : 'busy', | ||||
'className' => 'fc-type-freebusy', | 'className' => 'fc-type-freebusy', | ||||
'organizer' => array( | 'organizer' => [ | ||||
'email' => $this->userdata['mail'], | 'email' => $this->userdata['mail'], | ||||
'name' => $this->userdata['displayname'], | 'name' => isset($this->userdata['displayname']) ? $this->userdata['displayname'] : null, | ||||
), | ], | ||||
); | ]; | ||||
// avoid duplicate entries | // avoid duplicate entries | ||||
$key = $this->time_key($event); | $key = $this->time_key($event); | ||||
if (!$this->timeindex[$key]) { | if (empty($this->timeindex[$key])) { | ||||
$this->events[$event['uid']] = $event; | $this->events[$event['uid']] = $event; | ||||
$this->timeindex[$key] = $event['uid']; | $this->timeindex[$key] = $event['uid']; | ||||
$count++; | $count++; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return $count; | return $count; | ||||
} | } | ||||
/** | /** | ||||
* Helper to build a key for the absolute time slot the given event convers | * Helper to build a key for the absolute time slot the given event convers | ||||
*/ | */ | ||||
private function time_key($event) | private function time_key($event) | ||||
{ | { | ||||
return sprintf('%s/%s', $event['start']->format('U'), is_object($event['end']) ? $event['end']->format('U') : '0'); | return sprintf('%s/%s', $event['start']->format('U'), is_object($event['end']) ? $event['end']->format('U') : '0'); | ||||
} | } | ||||
/** | /** | ||||
* Create a new event record | * Create a new event record | ||||
* | * | ||||
* @see calendar_driver::new_event() | * @see calendar_driver::new_event() | ||||
* | * | ||||
* @return mixed The created record ID on success, False on error | * @return mixed The created record ID on success, False on error | ||||
*/ | */ | ||||
public function insert_event($event) | public function insert_event($event) | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Update a specific event record | * Update a specific event record | ||||
* | * | ||||
* @see calendar_driver::new_event() | * @see calendar_driver::new_event() | ||||
* @return boolean True on success, False on error | * @return bool True on success, False on error | ||||
*/ | */ | ||||
public function update_event($event, $exception_id = null) | public function update_event($event, $exception_id = null) | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Delete an event record | * Delete an event record | ||||
* | * | ||||
* @see calendar_driver::remove_event() | * @see calendar_driver::remove_event() | ||||
* @return boolean True on success, False on error | * @return bool True on success, False on error | ||||
*/ | */ | ||||
public function delete_event($event, $force = true) | public function delete_event($event, $force = true) | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Restore deleted event record | * Restore deleted event record | ||||
* | * | ||||
* @see calendar_driver::undelete_event() | * @see calendar_driver::undelete_event() | ||||
* @return boolean True on success, False on error | * @return bool True on success, False on error | ||||
*/ | */ | ||||
public function restore_event($event) | public function restore_event($event) | ||||
{ | { | ||||
return false; | return false; | ||||
} | } | ||||
} | } |