New Roundcube plugin for Kolab 3.0 storage layer
This commit is contained in:
parent
3df2e08844
commit
38bdff1cd0
7 changed files with 1309 additions and 0 deletions
133
plugins/libkolab/lib/kolab_format.php
Normal file
133
plugins/libkolab/lib/kolab_format.php
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Kolab format model class wrapping libkolabxml bindings
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
abstract class kolab_format
|
||||
{
|
||||
public static $timezone;
|
||||
|
||||
/**
|
||||
* Factory method to instantiate a kolab_format object of the given type
|
||||
*/
|
||||
public static function factory($type)
|
||||
{
|
||||
if (!isset(self::$timezone))
|
||||
self::$timezone = new DateTimeZone('UTC');
|
||||
|
||||
$suffix = preg_replace('/[^a-z]+/', '', $type);
|
||||
$classname = 'kolab_format_' . $suffix;
|
||||
if (class_exists($classname))
|
||||
return new $classname();
|
||||
|
||||
return PEAR::raiseError(sprintf("Failed to load Kolab Format wrapper for type %s", $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random UID for Kolab objects
|
||||
*
|
||||
* @return string MD5 hash with a unique value
|
||||
*/
|
||||
public static function generate_uid()
|
||||
{
|
||||
return md5(uniqid(mt_rand(), true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given date/time value into a c_DateTime object
|
||||
*
|
||||
* @param mixed Date/Time value either as unix timestamp, date string or PHP DateTime object
|
||||
* @param DateTimeZone The timezone the date/time is in. Use global default if empty
|
||||
* @param boolean True of the given date has no time component
|
||||
* @return c_DateTime The libkolabxml date/time object or null on error
|
||||
*/
|
||||
public static function getDateTime($datetime, $tz = null, $dateonly = false)
|
||||
{
|
||||
if (!$tz) $tz = self::$timezone;
|
||||
$result = null;
|
||||
|
||||
if (is_numeric($datetime))
|
||||
$datetime = new DateTime('@'.$datetime, $tz);
|
||||
else if (is_string($datetime))
|
||||
$datetime = new DateTime($datetime, $tz);
|
||||
|
||||
if (is_a($datetime, 'DateTime')) {
|
||||
$result = new KolabDateTime();
|
||||
$result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'), 0, 0, 0);
|
||||
|
||||
if (!$dateonly)
|
||||
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
|
||||
if ($tz)
|
||||
$result->setTimezone($tz->getName());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a libkolabxml vector to a PHP array
|
||||
*
|
||||
* @param object vector Object
|
||||
* @return array Indexed array contaning vector elements
|
||||
*/
|
||||
public static function vector2array($vec)
|
||||
{
|
||||
$arr = array();
|
||||
for ($i=0; $i < $vec->size(); $i++)
|
||||
$arr[] = $vec->get($i);
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Kolab object data from the given XML block
|
||||
*
|
||||
* @param string XML data
|
||||
*/
|
||||
abstract public function load($xml);
|
||||
|
||||
/**
|
||||
* Set properties to the kolabformat object
|
||||
*
|
||||
* @param array Object data as hash array
|
||||
*/
|
||||
abstract public function set(&$object);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract public function is_valid();
|
||||
|
||||
/**
|
||||
* Write object data to XML format
|
||||
*
|
||||
* @return string XML data
|
||||
*/
|
||||
abstract public function write();
|
||||
|
||||
/**
|
||||
* Convert the Kolab object into a hash array data structure
|
||||
*
|
||||
* @return array Kolab object data as hash array
|
||||
*/
|
||||
abstract public function to_array();
|
||||
|
||||
}
|
244
plugins/libkolab/lib/kolab_format_contact.php
Normal file
244
plugins/libkolab/lib/kolab_format_contact.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
|
||||
class kolab_format_contact extends kolab_format
|
||||
{
|
||||
public $CTYPE = 'application/vcard+xml';
|
||||
|
||||
private $data;
|
||||
private $obj;
|
||||
|
||||
// old Kolab 2 format field map
|
||||
private $kolab2_fieldmap = array(
|
||||
// kolab => roundcube
|
||||
'full-name' => 'name',
|
||||
'given-name' => 'firstname',
|
||||
'middle-names' => 'middlename',
|
||||
'last-name' => 'surname',
|
||||
'prefix' => 'prefix',
|
||||
'suffix' => 'suffix',
|
||||
'nick-name' => 'nickname',
|
||||
'organization' => 'organization',
|
||||
'department' => 'department',
|
||||
'job-title' => 'jobtitle',
|
||||
'initials' => 'initials',
|
||||
'birthday' => 'birthday',
|
||||
'anniversary' => 'anniversary',
|
||||
'phone' => 'phone',
|
||||
'im-address' => 'im',
|
||||
'web-page' => 'website',
|
||||
'office-location' => 'officelocation',
|
||||
'profession' => 'profession',
|
||||
'manager-name' => 'manager',
|
||||
'assistant' => 'assistant',
|
||||
'spouse-name' => 'spouse',
|
||||
'children' => 'children',
|
||||
'body' => 'notes',
|
||||
'pgp-publickey' => 'pgppublickey',
|
||||
'free-busy-url' => 'freebusyurl',
|
||||
'gender' => 'gender',
|
||||
);
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->obj = new Contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Contact object data from the given XML block
|
||||
*
|
||||
* @param string XML data
|
||||
*/
|
||||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readContact($xml, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Contact object data to XML format
|
||||
*
|
||||
* @return string XML data
|
||||
*/
|
||||
public function write()
|
||||
{
|
||||
return kolabformat::writeContact($this->obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set contact properties to the kolabformat object
|
||||
*
|
||||
* @param array Contact data as hash array
|
||||
*/
|
||||
public function set(&$object)
|
||||
{
|
||||
// set some automatic values if missing
|
||||
if (empty($object['uid']))
|
||||
$object['uid'] = self::generate_uid();
|
||||
|
||||
if (false && !$this->obj->created()) {
|
||||
if (!empty($object['created']))
|
||||
$object['created'] = new DateTime('now', self::$timezone);
|
||||
$this->obj->setCreated(self::getDateTime($object['created']));
|
||||
}
|
||||
|
||||
// do the hard work of setting object values
|
||||
$this->obj->setUid($object['uid']);
|
||||
|
||||
$nc = new NameComponents;
|
||||
// surname
|
||||
$sn = new vectors();
|
||||
$sn->push($object['surname']);
|
||||
$nc->setSurnames($sn);
|
||||
// firstname
|
||||
$gn = new vectors();
|
||||
$gn->push($object['firstname']);
|
||||
$nc->setGiven($gn);
|
||||
// middle name
|
||||
$mn = new vectors();
|
||||
if ($object['middlename'])
|
||||
$mn->push($object['middlename']);
|
||||
$nc->setAdditional($mn);
|
||||
// prefix
|
||||
$px = new vectors();
|
||||
if ($object['prefix'])
|
||||
$px->push($object['prefix']);
|
||||
$nc->setPrefixes($px);
|
||||
// suffix
|
||||
$sx = new vectors();
|
||||
if ($object['suffix'])
|
||||
$sx->push($object['suffix']);
|
||||
$nc->setSuffixes($sx);
|
||||
|
||||
$this->obj->setNameComponents($nc);
|
||||
$this->obj->setName($object['name']);
|
||||
|
||||
// email addresses
|
||||
$emails = new vectors;
|
||||
foreach ($object['email'] as $em)
|
||||
$emails->push($em);
|
||||
$this->obj->setEmailAddresses($emails);
|
||||
|
||||
// addresses
|
||||
$adrs = new vectoraddress;
|
||||
foreach ($object['address'] as $address) {
|
||||
$adr = new Address;
|
||||
$adr->setTypes($address['type'] == 'work' ? Address::Work : Address::Home);
|
||||
if ($address['street'])
|
||||
$adr->setStreet($address['street']);
|
||||
if ($address['locality'])
|
||||
$adr->setLocality($address['locality']);
|
||||
if ($address['code'])
|
||||
$adr->setCode($address['code']);
|
||||
if ($address['region'])
|
||||
$adr->setRegion($address['region']);
|
||||
if ($address['country'])
|
||||
$adr->setCountry($address['country']);
|
||||
|
||||
$adrs->push($adr);
|
||||
}
|
||||
$this->obj->setAddresses($adrs);
|
||||
|
||||
// cache this data
|
||||
$this->data = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function is_valid()
|
||||
{
|
||||
return $this->data || (is_object($this->obj) && true /*$this->obj->isValid()*/);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert the Contact object into a hash array data structure
|
||||
*
|
||||
* @return array Contact data as hash array
|
||||
*/
|
||||
public function to_array()
|
||||
{
|
||||
// return cached result
|
||||
if (!empty($this->data))
|
||||
return $this->data;
|
||||
|
||||
// TODO: read object properties into local data object
|
||||
$object = array(
|
||||
'uid' => $this->obj->uid(),
|
||||
# 'changed' => $this->obj->lastModified(),
|
||||
'name' => $this->obj->name(),
|
||||
);
|
||||
|
||||
$nc = $this->obj->nameComponents();
|
||||
$object['surname'] = join(' ', self::vector2array($nc->surnames()));
|
||||
$object['firstname'] = join(' ', self::vector2array($nc->given()));
|
||||
$object['middlename'] = join(' ', self::vector2array($nc->additional()));
|
||||
$object['prefix'] = join(' ', self::vector2array($nc->prefixes()));
|
||||
$object['suffix'] = join(' ', self::vector2array($nc->suffixes()));
|
||||
|
||||
$object['email'] = self::vector2array($this->obj->emailAddresses());
|
||||
|
||||
$addresses = $this->obj->addresses();
|
||||
for ($i=0; $i < $addresses->size(); $i++) {
|
||||
$adr = $addresses->get($i);
|
||||
$object['address'][] = array(
|
||||
'type' => $adr->types() == Address::Work ? 'work' : 'home',
|
||||
'street' => $adr->street(),
|
||||
'code' => $adr->code(),
|
||||
'locality' => $adr->locality(),
|
||||
'region' => $adr->region(),
|
||||
'country' => $adr->country()
|
||||
);
|
||||
}
|
||||
|
||||
$this->data = $object;
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from old Kolab2 format
|
||||
*/
|
||||
public function fromkolab2($record)
|
||||
{
|
||||
$object = array(
|
||||
'uid' => $record['uid'],
|
||||
'email' => array(),
|
||||
'phone' => array(),
|
||||
);
|
||||
|
||||
foreach ($this->kolab2_fieldmap as $kolab => $rcube) {
|
||||
if (is_array($record[$kolab]) || strlen($record[$kolab]))
|
||||
$object[$rcube] = $record[$kolab];
|
||||
}
|
||||
|
||||
if (isset($record['gender']))
|
||||
$object['gender'] = $this->gender_map[$record['gender']];
|
||||
|
||||
foreach ((array)$record['email'] as $i => $email)
|
||||
$object['email'][] = $email['smtp-address'];
|
||||
|
||||
if (!$record['email'] && $record['emails'])
|
||||
$object['email'] = preg_split('/,\s*/', $record['emails']);
|
||||
|
||||
if (is_array($record['address'])) {
|
||||
foreach ($record['address'] as $i => $adr) {
|
||||
$object['address'][] = array(
|
||||
'type' => $adr['type'],
|
||||
'street' => $adr['street'],
|
||||
'locality' => $adr['locality'],
|
||||
'code' => $adr['postal-code'],
|
||||
'region' => $adr['region'],
|
||||
'country' => $adr['country'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// photo is stored as separate attachment
|
||||
if ($record['picture'] && ($att = $record['_attachments'][$record['picture']])) {
|
||||
$object['photo'] = $att['content'] ? $att['content'] : $this->contactstorage->getAttachment($att['key']);
|
||||
}
|
||||
|
||||
// remove empty fields
|
||||
$this->data = array_filter($object);
|
||||
}
|
||||
}
|
126
plugins/libkolab/lib/kolab_format_distributionlist.php
Normal file
126
plugins/libkolab/lib/kolab_format_distributionlist.php
Normal file
|
@ -0,0 +1,126 @@
|
|||
<?php
|
||||
|
||||
|
||||
class kolab_format_distributionlist extends kolab_format
|
||||
{
|
||||
public $CTYPE = 'application/vcard+xml';
|
||||
|
||||
private $data;
|
||||
private $obj;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$obj = new DistList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Kolab object data from the given XML block
|
||||
*
|
||||
* @param string XML data
|
||||
*/
|
||||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readDistlist($xml, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write object data to XML format
|
||||
*
|
||||
* @return string XML data
|
||||
*/
|
||||
public function write()
|
||||
{
|
||||
return kolabformat::writeDistlist($this->obj);
|
||||
}
|
||||
|
||||
public function set(&$object)
|
||||
{
|
||||
// TODO: do the hard work of setting object values
|
||||
}
|
||||
|
||||
public function is_valid()
|
||||
{
|
||||
return $this->data || (is_object($this->obj) && true /*$this->obj->isValid()*/);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from old Kolab2 format
|
||||
*/
|
||||
public function fromkolab2($record)
|
||||
{
|
||||
$object = array(
|
||||
'uid' => $record['uid'],
|
||||
'changed' => $record['last-modification-date'],
|
||||
'name' => $record['last-name'],
|
||||
'member' => array(),
|
||||
);
|
||||
|
||||
foreach ($record['member'] as $member) {
|
||||
$object['member'][] = array(
|
||||
'mailto' => $member['smtp-address'],
|
||||
'name' => $member['display-name'],
|
||||
'uid' => $member['uid'],
|
||||
);
|
||||
}
|
||||
|
||||
$this->data = $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the Distlist object into a hash array data structure
|
||||
*
|
||||
* @return array Distribution list data as hash array
|
||||
*/
|
||||
public function to_array()
|
||||
{
|
||||
// return cached result
|
||||
if (!empty($this->data))
|
||||
return $this->data;
|
||||
|
||||
// read object properties
|
||||
$object = array(
|
||||
'uid' => $this->obj->uid(),
|
||||
# 'changed' => $this->obj->lastModified(),
|
||||
'name' => $this->obj->name(),
|
||||
'member' => array(),
|
||||
);
|
||||
|
||||
$members = $this->obj->members();
|
||||
for ($i=0; $i < $members->size(); $i++) {
|
||||
$adr = self::decode_member($members->get($i));
|
||||
if ($adr[0]['mailto'])
|
||||
$object['member'][] = array(
|
||||
'mailto' => $adr[0]['mailto'],
|
||||
'name' => $adr[0]['name'],
|
||||
'uid' => '????',
|
||||
);
|
||||
}
|
||||
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a valid Mailto URL according to RFC 822
|
||||
*
|
||||
* @param string E-mail address
|
||||
* @param string Person name
|
||||
* @return string Formatted string
|
||||
*/
|
||||
public static function format_member($email, $name = '')
|
||||
{
|
||||
// let Roundcube internals do the job
|
||||
return 'mailto:' . format_email_recipient($email, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a mailto: url into a structured member component
|
||||
*
|
||||
* @param string RFC 822 mailto: string
|
||||
* @return array Hash array with member properties
|
||||
*/
|
||||
public static function decode_member($str)
|
||||
{
|
||||
$adr = rcube_mime::decode_address_list(preg_replace('/^mailto:/', '', $str));
|
||||
return $adr[0];
|
||||
}
|
||||
}
|
46
plugins/libkolab/lib/kolab_format_event.php
Normal file
46
plugins/libkolab/lib/kolab_format_event.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
|
||||
class kolab_format_event extends kolab_format
|
||||
{
|
||||
public $CTYPE = 'application/calendar+xml';
|
||||
|
||||
private $data;
|
||||
private $obj;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$obj = new Event;
|
||||
}
|
||||
|
||||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readEvent($xml, false);
|
||||
}
|
||||
|
||||
public function write()
|
||||
{
|
||||
return kolabformat::writeEvent($this->obj);
|
||||
}
|
||||
|
||||
public function set(&$object)
|
||||
{
|
||||
// TODO: do the hard work of setting object values
|
||||
}
|
||||
|
||||
public function is_valid()
|
||||
{
|
||||
return is_object($this->obj) && $this->obj->isValid();
|
||||
}
|
||||
|
||||
public function fromkolab2($object)
|
||||
{
|
||||
$this->data = $object;
|
||||
}
|
||||
|
||||
public function to_array()
|
||||
{
|
||||
// TODO: read object properties
|
||||
return $this->data;
|
||||
}
|
||||
}
|
206
plugins/libkolab/lib/kolab_storage.php
Normal file
206
plugins/libkolab/lib/kolab_storage.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Kolab storage class providing static methods to access groupware objects on a Kolab server.
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
class kolab_storage
|
||||
{
|
||||
const CTYPE_KEY = '/shared/vendor/kolab/folder-type';
|
||||
|
||||
public static $last_error;
|
||||
|
||||
private static $ready = false;
|
||||
private static $config;
|
||||
private static $cache;
|
||||
private static $imap;
|
||||
|
||||
|
||||
/**
|
||||
* Setup the environment needed by the libs
|
||||
*/
|
||||
public static function setup()
|
||||
{
|
||||
if (self::$ready)
|
||||
return;
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
self::$config = $rcmail->config;
|
||||
self::$imap = $rcmail->get_storage();
|
||||
self::$ready = class_exists('kolabformat') && $rcmail->storage_connect() &&
|
||||
(self::$imap->get_capability('METADATA') || self::$imap->get_capability('ANNOTATEMORE') || self::$imap->get_capability('ANNOTATEMORE2'));
|
||||
|
||||
if (self::$ready) {
|
||||
// set imap options
|
||||
self::$imap->set_options(array(
|
||||
'skip_deleted' => true,
|
||||
'threading' => false,
|
||||
));
|
||||
self::$imap->set_pagesize(9999);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of storage folders for the given data type
|
||||
*
|
||||
* @param string Data type to list folders for (contact,event,task,note)
|
||||
*
|
||||
* @return array List of Kolab_Folder objects (folder names in UTF7-IMAP)
|
||||
*/
|
||||
public static function get_folders($type)
|
||||
{
|
||||
self::setup();
|
||||
$folders = array();
|
||||
|
||||
if (self::$ready) {
|
||||
foreach ((array)self::$imap->list_folders('', '*', $type) as $foldername) {
|
||||
$folders[$foldername] = new kolab_storage_folder($foldername, self::$imap);
|
||||
}
|
||||
}
|
||||
|
||||
return $folders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for a specific storage folder
|
||||
*
|
||||
* @param string IMAP folder to access (UTF7-IMAP)
|
||||
* @return object Kolab_Folder The folder object
|
||||
*/
|
||||
public static function get_folder($folder)
|
||||
{
|
||||
self::setup();
|
||||
return self::$ready ? new kolab_storage_folder($folder, null, self::$imap) : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static function get_freebusy_server()
|
||||
{
|
||||
return unslashify(self::$config->get('kolab_freebusy_server', 'https://' . $_SESSION['imap_host'] . '/freebusy'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compose an URL to query the free/busy status for the given user
|
||||
*/
|
||||
public static function get_freebusy_url($email)
|
||||
{
|
||||
return self::get_freebusy_server() . '/' . $email . '.ifb';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates folder ID from folder name
|
||||
*
|
||||
* @param string $folder Folder name (UTF7-IMAP)
|
||||
*
|
||||
* @return string Folder ID string
|
||||
*/
|
||||
public static function folder_id($folder)
|
||||
{
|
||||
return asciiwords(strtr($folder, '/.-', '___'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for human-readable name of Kolab object (folder)
|
||||
* See http://wiki.kolab.org/UI-Concepts/Folder-Listing for reference
|
||||
*
|
||||
* @param string $folder IMAP folder name (UTF7-IMAP)
|
||||
* @param string $folder_ns Will be set to namespace name of the folder
|
||||
*
|
||||
* @return string Name of the folder-object
|
||||
*/
|
||||
public static function object_name($folder, &$folder_ns=null)
|
||||
{
|
||||
self::setup();
|
||||
|
||||
$found = false;
|
||||
$namespace = self::$imap->get_namespace();
|
||||
|
||||
if (!empty($namespace['shared'])) {
|
||||
foreach ($namespace['shared'] as $ns) {
|
||||
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
|
||||
$prefix = '';
|
||||
$folder = substr($folder, strlen($ns[0]));
|
||||
$delim = $ns[1];
|
||||
$found = true;
|
||||
$folder_ns = 'shared';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found && !empty($namespace['other'])) {
|
||||
foreach ($namespace['other'] as $ns) {
|
||||
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
|
||||
// remove namespace prefix
|
||||
$folder = substr($folder, strlen($ns[0]));
|
||||
$delim = $ns[1];
|
||||
// get username
|
||||
$pos = strpos($folder, $delim);
|
||||
if ($pos) {
|
||||
$prefix = '('.substr($folder, 0, $pos).') ';
|
||||
$folder = substr($folder, $pos+1);
|
||||
}
|
||||
else {
|
||||
$prefix = '('.$folder.')';
|
||||
$folder = '';
|
||||
}
|
||||
$found = true;
|
||||
$folder_ns = 'other';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$found && !empty($namespace['personal'])) {
|
||||
foreach ($namespace['personal'] as $ns) {
|
||||
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
|
||||
// remove namespace prefix
|
||||
$folder = substr($folder, strlen($ns[0]));
|
||||
$prefix = '';
|
||||
$delim = $ns[1];
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($delim))
|
||||
$delim = self::$imap->get_hierarchy_delimiter();
|
||||
|
||||
$folder = rcube_charset::convert($folder, 'UTF7-IMAP');
|
||||
$folder = str_replace($delim, ' » ', $folder);
|
||||
|
||||
if ($prefix)
|
||||
$folder = $prefix . ' ' . $folder;
|
||||
|
||||
if (!$folder_ns)
|
||||
$folder_ns = 'personal';
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
}
|
498
plugins/libkolab/lib/kolab_storage_folder.php
Normal file
498
plugins/libkolab/lib/kolab_storage_folder.php
Normal file
|
@ -0,0 +1,498 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* The kolab_storage_folder class represents an IMAP folder on the Kolab server.
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
class kolab_storage_folder
|
||||
{
|
||||
const KTYPE_PREFIX = 'application/x-vnd.kolab.';
|
||||
|
||||
/**
|
||||
* The folder name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* The type of this folder.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
private $type_annotation;
|
||||
private $subpath;
|
||||
private $imap;
|
||||
private $info;
|
||||
private $owner;
|
||||
private $uid2msg = array();
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
function __construct($name, $imap = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->imap = is_object($imap) ? $imap : rcmail::get_instance()->get_storage();
|
||||
$this->imap->set_folder($this->name);
|
||||
|
||||
$metadata = $this->imap->get_metadata($this->name, array(kolab_storage::CTYPE_KEY));
|
||||
$this->type_annotation = $metadata[$this->name][kolab_storage::CTYPE_KEY];
|
||||
$this->type = reset(explode('.', $this->type_annotation));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function get_folder_info()
|
||||
{
|
||||
if (!isset($this->info))
|
||||
$this->info = $this->imap->folder_info($this->name);
|
||||
|
||||
return $this->info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the owner of the folder.
|
||||
*
|
||||
* @return string The owner of this folder.
|
||||
*/
|
||||
public function get_owner()
|
||||
{
|
||||
// return cached value
|
||||
if (isset($this->owner))
|
||||
return $this->owner;
|
||||
|
||||
$info = $this->get_folder_info();
|
||||
$rcmail = rcmail::get_instance();
|
||||
|
||||
switch ($info['namespace']) {
|
||||
case 'personal':
|
||||
$this->owner = $rcmail->user->get_username();
|
||||
break;
|
||||
|
||||
case 'shared':
|
||||
$this->owner = 'anonymous';
|
||||
break;
|
||||
|
||||
default:
|
||||
$owner = '';
|
||||
list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']);
|
||||
if (strpos($user, '@') === false) {
|
||||
$domain = strstr($rcmail->user->get_username(), '@');
|
||||
if (!empty($domain))
|
||||
$user .= $domain;
|
||||
}
|
||||
$this->owner = $user;
|
||||
break;
|
||||
}
|
||||
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for the name of the namespace to which the IMAP folder belongs
|
||||
*
|
||||
* @return string Name of the namespace (personal, other, shared)
|
||||
*/
|
||||
public function get_namespace()
|
||||
{
|
||||
return $this->imap->folder_namespace($this->name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get IMAP ACL information for this folder
|
||||
*
|
||||
* @return string Permissions as string
|
||||
*/
|
||||
function get_acl()
|
||||
{
|
||||
return join('', (array)$this->imap->get_acl($this->name));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all Kolab objects of the given type
|
||||
*
|
||||
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
|
||||
* @return array List of Kolab data objects (each represented as hash array)
|
||||
*/
|
||||
function get_objects($type = null)
|
||||
{
|
||||
if (!$type) $type = $this->type;
|
||||
|
||||
// search by object type
|
||||
$ctype = self::KTYPE_PREFIX . $type;
|
||||
$search = 'HEADER X-Kolab-Type ' . $ctype;
|
||||
|
||||
$index = $this->imap->search_once($this->name, $search);
|
||||
$results = array();
|
||||
|
||||
// fetch all messages from IMAP
|
||||
foreach ($index->get() as $msguid) {
|
||||
if ($object = $this->read_object($msguid, $type)) {
|
||||
$results[] = $object;
|
||||
$this->uid2msg[$object['uid']] = $msguid;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: write $this->uid2msg to cache
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter for a single Kolab object, identified by its UID
|
||||
*
|
||||
* @param string Object UID
|
||||
* @return array The Kolab object represented as hash array
|
||||
*/
|
||||
public function get_object($uid)
|
||||
{
|
||||
$msguid = $this->uid2msguid($uid);
|
||||
if ($msguid && ($object = $this->read_object($msguid)))
|
||||
return $object;
|
||||
|
||||
return array('uid' => $uid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch a Kolab object attachment which is stored in a separate part
|
||||
* of the mail MIME message that represents the Kolab record.
|
||||
*
|
||||
* @param string Object's UID
|
||||
* @param string The attachment key stored in the Kolab XML
|
||||
* @return mixed The attachment content as binary string
|
||||
*/
|
||||
public function get_attachment($uid, $key)
|
||||
{
|
||||
// TODO: implement this
|
||||
|
||||
if ($msguid = $this->uid2msguid($uid)) {
|
||||
$message = new rcube_message($msguid);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function read_object($msguid, $type = null)
|
||||
{
|
||||
if (!$type) $type = $this->type;
|
||||
$ctype= self::KTYPE_PREFIX . $type;
|
||||
|
||||
$this->imap->set_folder($this->name);
|
||||
$message = new rcube_message($msguid);
|
||||
|
||||
// get XML part
|
||||
foreach ((array)$message->attachments as $part) {
|
||||
if ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype)) {
|
||||
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$xml) {
|
||||
raise_error(array(
|
||||
'code' => 600,
|
||||
'type' => 'php',
|
||||
'file' => __FILE__,
|
||||
'line' => __LINE__,
|
||||
'message' => "Could not find Kolab data part in message " . $this->name . ':' . $uid,
|
||||
), true);
|
||||
return false;
|
||||
}
|
||||
|
||||
$format = kolab_format::factory($type);
|
||||
|
||||
// check kolab format version
|
||||
if (strpos($xml, '<' . $type) !== false) {
|
||||
// old Kolab 2.0 format detected
|
||||
$handler = Horde_Kolab_Format::factory('XML', $type);
|
||||
if (is_object($handler) && is_a($handler, 'PEAR_Error')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// XML-to-array
|
||||
$object = $handler->load($xml);
|
||||
$format->fromkolab2($object);
|
||||
}
|
||||
else {
|
||||
// load Kolab 3 format using libkolabxml
|
||||
$format->load($xml);
|
||||
}
|
||||
|
||||
if ($format->is_valid()) {
|
||||
$object = $format->to_array();
|
||||
$object['_msguid'] = $msguid;
|
||||
$object['_mailbox'] = $this->name;
|
||||
return $object;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save an object in this folder.
|
||||
*
|
||||
* @param array $object The array that holds the data of the object.
|
||||
* @param string $type The type of the kolab object.
|
||||
* @param string $uid The UID of the old object if it existed before
|
||||
* @return boolean True on success, false on error
|
||||
*/
|
||||
public function save(&$object, $type, $uid = null)
|
||||
{
|
||||
if (!$type)
|
||||
$type = $this->type;
|
||||
|
||||
if ($raw_msg = $this->build_message($object, $type)) {
|
||||
$result = $this->imap->save_message($this->name, $raw_msg, '', false);
|
||||
|
||||
// delete old message
|
||||
if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
|
||||
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
|
||||
}
|
||||
else if ($result && $uid && ($msguid = $this->uid2msguid($uid))) {
|
||||
$this->imap->delete_message($msguid, $this->name);
|
||||
}
|
||||
|
||||
// TODO: update cache with new UID
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete the specified object from this folder.
|
||||
*
|
||||
* @param array $object The Kolab object to delete
|
||||
* @param boolean $trigger Should the folder be triggered?
|
||||
* @param boolean $expunge Should the folder be expunged?
|
||||
*
|
||||
* @return boolean True if successful, false on error
|
||||
*/
|
||||
function delete($object, $trigger = true, $expunge = true)
|
||||
{
|
||||
if (!empty($object['_msguid'])) {
|
||||
return $this->imap->delete_message($object['_msguid'], $this->name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
return $this->imap->clear_folder($this->name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve an object UID into an IMAP message UID
|
||||
*/
|
||||
private function uid2msguid($uid)
|
||||
{
|
||||
if (!isset($this->uid2msg[$uid])) {
|
||||
// use IMAP SEARCH to get the right message
|
||||
$index = $this->imap->search_once($this->name, 'HEADER SUBJECT ' . $uid);
|
||||
$results = $index->get();
|
||||
$this->uid2msg[$uid] = $results[0];
|
||||
|
||||
// TODO: cache this lookup
|
||||
}
|
||||
|
||||
return $this->uid2msg[$uid];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates source of the configuration object message
|
||||
*/
|
||||
private function build_message(&$object, $type)
|
||||
{
|
||||
$format = kolab_format::factory($type);
|
||||
$format->set($object);
|
||||
$xml = $format->write();
|
||||
|
||||
if (!$format->is_valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mime = new Mail_mime("\r\n");
|
||||
$rcmail = rcmail::get_instance();
|
||||
$headers = array();
|
||||
|
||||
if ($ident = $rcmail->user->get_identity()) {
|
||||
$headers['From'] = $ident['email'];
|
||||
$headers['To'] = $ident['email'];
|
||||
}
|
||||
$headers['Date'] = date('r');
|
||||
$headers['X-Kolab-Type'] = self::KTYPE_PREFIX . $type;
|
||||
$headers['Subject'] = $object['uid'];
|
||||
// $headers['Message-ID'] = rcmail_gen_message_id();
|
||||
$headers['User-Agent'] = $rcmail->config->get('useragent');
|
||||
|
||||
$mime->headers($headers);
|
||||
$mime->setTXTBody('This is a Kolab Groupware object. '
|
||||
. 'To view this object you will need an email client that understands the Kolab Groupware format. '
|
||||
. "For a list of such email clients please visit http://www.kolab.org/kolab2-clients.html\n\n");
|
||||
|
||||
$mime->addAttachment($xml,
|
||||
$format->CTYPE,
|
||||
'kolab.xml',
|
||||
false, '8bit', 'attachment', RCMAIL_CHARSET, '', '',
|
||||
$rcmail->config->get('mime_param_folding') ? 'quoted-printable' : null,
|
||||
$rcmail->config->get('mime_param_folding') == 2 ? 'quoted-printable' : null,
|
||||
'', RCMAIL_CHARSET
|
||||
);
|
||||
|
||||
return $mime->getMessage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers any required updates after changes within the
|
||||
* folder. This is currently only required for handling free/busy
|
||||
* information with Kolab.
|
||||
*
|
||||
* @return boolean|PEAR_Error True if successfull.
|
||||
*/
|
||||
public function trigger()
|
||||
{
|
||||
$owner = $this->get_owner();
|
||||
|
||||
switch($this->type) {
|
||||
case 'event':
|
||||
$url = sprintf('%s/trigger/%s/%s.pfb', kolab_storage::get_freebusy_server(), $owner, $this->subpath);
|
||||
break;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
$result = $this->trigger_url($url);
|
||||
if (is_a($result, 'PEAR_Error')) {
|
||||
return PEAR::raiseError(sprintf("Failed triggering folder %s. Error was: %s"),
|
||||
$this->name, $result->getMessage());
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a URL.
|
||||
*
|
||||
* @param string $url The URL to be triggered.
|
||||
* @return boolean|PEAR_Error True if successfull.
|
||||
*/
|
||||
private function trigger_url($url)
|
||||
{
|
||||
// TBD.
|
||||
return PEAR::raiseError("Feature not implemented.");
|
||||
}
|
||||
|
||||
|
||||
/* Legacy methods to keep compatibility with the old Horde Kolab_Storage classes */
|
||||
|
||||
/**
|
||||
* Compatibility method
|
||||
*/
|
||||
public function getOwner()
|
||||
{
|
||||
console("Call to deprecated method kolab_storage_folder::getOwner()");
|
||||
return $this->get_owner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IMAP ACL information for this folder
|
||||
*/
|
||||
public function getMyRights()
|
||||
{
|
||||
return $this->get_acl();
|
||||
}
|
||||
|
||||
/**
|
||||
* NOP to stay compatible with the formerly used Horde classes
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* List all Kolab objects of the given type
|
||||
*/
|
||||
public function getObjects($type = null)
|
||||
{
|
||||
return $this->get_objects($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for a single Kolab object, identified by its UID
|
||||
*/
|
||||
public function getObject($uid)
|
||||
{
|
||||
return $this->get_object($uid);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function getAttachment($key)
|
||||
{
|
||||
PEAR::raiseError("Call to deprecated method not returning anything.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias function of delete()
|
||||
*/
|
||||
public function deleteMessage($id, $trigger = true, $expunge = true)
|
||||
{
|
||||
return $this->delete(array('_msguid' => $id), $trigger, $expunge);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function deleteAll()
|
||||
{
|
||||
return $this->delete_all();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
56
plugins/libkolab/libkolab.php
Normal file
56
plugins/libkolab/libkolab.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Kolab core library
|
||||
*
|
||||
* Plugin to setup a basic environment for the interaction with a Kolab server.
|
||||
* Other Kolab-related plugins will depend on it and can use the library classes
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
class libkolab extends rcube_plugin
|
||||
{
|
||||
/**
|
||||
* Required startup method of a Roundcube plugin
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// load local config
|
||||
$this->load_config();
|
||||
|
||||
// extend include path to load bundled lib classes
|
||||
$include_path = $this->home . '/lib' . PATH_SEPARATOR . ini_get('include_path');
|
||||
set_include_path($include_path);
|
||||
|
||||
$rcmail = rcmail::get_instance();
|
||||
kolab_format::$timezone = new DateTimeZone($rcmail->config->get('timezone', 'GMT'));
|
||||
|
||||
// load (old) dependencies
|
||||
require_once 'Horde/Util.php';
|
||||
require_once 'Horde/Kolab/Format.php';
|
||||
require_once 'Horde/Kolab/Format/XML.php';
|
||||
require_once 'Horde/Kolab/Format/XML/contact.php';
|
||||
require_once 'Horde/Kolab/Format/XML/event.php';
|
||||
|
||||
String::setDefaultCharset('UTF-8');
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue