Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F9785678
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
73 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
index 08ba20a8..19bea06c 100644
--- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
+++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
@@ -1,1119 +1,1125 @@
<?php
/**
* Backend class for a custom address book
*
* This part of the Roundcube+Kolab integration and connects the
* rcube_addressbook interface with the kolab_storage wrapper from libkolab
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, 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/>.
*
* @see rcube_addressbook
*/
class rcube_kolab_contacts extends rcube_addressbook
{
public $primary_key = 'ID';
public $readonly = true;
public $editable = false;
public $undelete = true;
public $groups = true;
public $coltypes = array(
'name' => array('limit' => 1),
'firstname' => array('limit' => 1),
'surname' => array('limit' => 1),
'middlename' => array('limit' => 1),
'prefix' => array('limit' => 1),
'suffix' => array('limit' => 1),
'nickname' => array('limit' => 1),
'jobtitle' => array('limit' => 1),
'organization' => array('limit' => 1),
'department' => array('limit' => 1),
'email' => array('subtypes' => null),
'phone' => array(),
- 'address' => array('subtypes' => array('home','business')),
+ 'address' => array('subtypes' => array('home','work')),
'officelocation' => array('type' => 'text', 'size' => 40, 'maxlength' => 50, 'limit' => 1,
'label' => 'kolab_addressbook.officelocation', 'category' => 'main'),
'website' => array('subtypes' => null),
'im' => array('subtypes' => null),
'gender' => array('limit' => 1),
'initials' => array('type' => 'text', 'size' => 6, 'maxlength' => 10, 'limit' => 1,
'label' => 'kolab_addressbook.initials', 'category' => 'personal'),
'birthday' => array('limit' => 1),
'anniversary' => array('limit' => 1),
'profession' => array('type' => 'text', 'size' => 40, 'maxlength' => 80, 'limit' => 1,
'label' => 'kolab_addressbook.profession', 'category' => 'personal'),
'manager' => array('limit' => 1),
'assistant' => array('limit' => 1),
'spouse' => array('limit' => 1),
'children' => array('type' => 'text', 'size' => 40, 'maxlength' => 80, 'limit' => 1,
'label' => 'kolab_addressbook.children', 'category' => 'personal'),
'pgppublickey' => array('type' => 'text', 'size' => 40, 'limit' => 1,
'label' => 'kolab_addressbook.pgppublickey'),
'freebusyurl' => array('type' => 'text', 'size' => 40, 'limit' => 1,
'label' => 'kolab_addressbook.freebusyurl'),
'notes' => array(),
'photo' => array(),
// TODO: define more Kolab-specific fields such as: role, language, latitude, longitude
);
/**
* vCard additional fields mapping
*/
public $vcard_map = array(
'profession' => 'X-PROFESSION',
'officelocation' => 'X-OFFICE-LOCATION',
'initials' => 'X-INITIALS',
'children' => 'X-CHILDREN',
'freebusyurl' => 'X-FREEBUSY-URL',
'pgppublickey' => 'KEY',
);
private $gid;
private $storagefolder;
private $contacts;
private $distlists;
private $groupmembers;
private $filter;
private $result;
private $namespace;
private $imap_folder = 'INBOX/Contacts';
public function __construct($imap_folder = null)
{
if ($imap_folder) {
$this->imap_folder = $imap_folder;
}
// extend coltypes configuration
$format = kolab_format::factory('contact');
$this->coltypes['phone']['subtypes'] = array_keys($format->phonetypes);
$this->coltypes['address']['subtypes'] = array_keys($format->addresstypes);
// set localized labels for proprietary cols
foreach ($this->coltypes as $col => $prop) {
if (is_string($prop['label']))
$this->coltypes[$col]['label'] = rcube_label($prop['label']);
}
// fetch objects from the given IMAP folder
$this->storagefolder = kolab_storage::get_folder($this->imap_folder);
$this->ready = !PEAR::isError($this->storagefolder);
// Set readonly and editable flags according to folder permissions
if ($this->ready) {
if ($this->storagefolder->get_owner() == $_SESSION['username']) {
$this->editable = true;
$this->readonly = false;
}
else {
$rights = $this->storagefolder->get_acl();
if (!PEAR::isError($rights)) {
if (strpos($rights, 'i') !== false)
$this->readonly = false;
if (strpos($rights, 'a') !== false || strpos($rights, 'x') !== false)
$this->editable = true;
}
}
}
}
/**
* Getter for the address book name to be displayed
*
* @return string Name of this address book
*/
public function get_name()
{
$folder = kolab_storage::object_name($this->imap_folder, $this->namespace);
return $folder;
}
/**
* Getter for the IMAP folder name
*
* @return string Name of the IMAP folder
*/
public function get_realname()
{
return $this->imap_folder;
}
/**
* 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()
{
if ($this->namespace === null) {
$this->namespace = $this->storagefolder->get_namespace();
}
return $this->namespace;
}
/**
* Setter for the current group
*/
public function set_group($gid)
{
$this->gid = $gid;
}
/**
* Save a search string for future listings
*
* @param mixed Search params to use in listing method, obtained by get_search_set()
*/
public function set_search_set($filter)
{
$this->filter = $filter;
}
/**
* Getter for saved search properties
*
* @return mixed Search properties used by this class
*/
public function get_search_set()
{
return $this->filter;
}
/**
* Reset saved results and search parameters
*/
public function reset()
{
$this->result = null;
$this->filter = null;
}
/**
* List all active contact groups of this source
*
* @param string Optional search string to match group name
* @return array Indexed list of contact groups, each a hash array
*/
function list_groups($search = null)
{
$this->_fetch_groups();
$groups = array();
foreach ((array)$this->distlists as $group) {
if (!$search || strstr(strtolower($group['name']), strtolower($search)))
$groups[$group['name']] = array('ID' => $group['ID'], 'name' => $group['name']);
}
// sort groups
ksort($groups, SORT_LOCALE_STRING);
return array_values($groups);
}
/**
* List the current set of contact records
*
* @param array List of cols to show
* @param int Only return this number of records, use negative values for tail
* @return array Indexed list of contact records, each a hash array
*/
public function list_records($cols=null, $subset=0)
{
$this->result = $this->count();
// list member of the selected group
if ($this->gid) {
$seen = array();
$this->result->count = 0;
foreach ((array)$this->distlists[$this->gid]['member'] as $member) {
// skip member that don't match the search filter
if (is_array($this->filter['ids']) && array_search($member['ID'], $this->filter['ids']) === false)
continue;
if ($this->contacts[$member['ID']] && !$seen[$member['ID']]++)
$this->result->count++;
}
$ids = array_keys($seen);
}
else {
$this->_fetch_contacts();
$ids = is_array($this->filter['ids']) ? $this->filter['ids'] : array_keys($this->contacts);
}
// sort data arrays according to desired list sorting
if ($count = count($ids)) {
uasort($this->contacts, array($this, '_sort_contacts_comp'));
// get sorted IDs
if ($count != count($this->contacts))
$ids = array_values(array_intersect(array_keys($this->contacts), $ids));
else
$ids = array_keys($this->contacts);
}
// fill contact data into the current result set
$start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first;
$last_row = min($subset != 0 ? $start_row + abs($subset) : $this->result->first + $this->page_size, $count);
for ($i = $start_row; $i < $last_row; $i++) {
if ($id = $ids[$i])
$this->result->add($this->contacts[$id]);
}
return $this->result;
}
/**
* Search records
*
* @param mixed $fields The field name of array of field names to search in
* @param mixed $value Search value (or array of values when $fields is array)
* @param int $mode Matching mode:
* 0 - partial (*abc*),
* 1 - strict (=),
* 2 - prefix (abc*)
* @param boolean $select True if results are requested, False if count only
* @param boolean $nocount True to skip the count query (select only)
* @param array $required List of fields that cannot be empty
*
* @return object rcube_result_set List of contact records and 'count' value
*/
public function search($fields, $value, $mode=0, $select=true, $nocount=false, $required=array())
{
$this->_fetch_contacts();
// search by ID
if ($fields == $this->primary_key) {
$ids = !is_array($value) ? explode(',', $value) : $value;
$result = new rcube_result_set();
foreach ($ids as $id) {
if ($rec = $this->get_record($id, true)) {
$result->add($rec);
$result->count++;
}
}
return $result;
}
else if ($fields == '*') {
$fields = array_keys($this->coltypes);
}
if (!is_array($fields))
$fields = array($fields);
if (!is_array($required) && !empty($required))
$required = array($required);
// advanced search
if (is_array($value)) {
$advanced = true;
$value = array_map('mb_strtolower', $value);
}
else
$value = mb_strtolower($value);
$scount = count($fields);
// build key name regexp
$regexp = '/^(' . implode($fields, '|') . ')(?:.*)$/';
// save searching conditions
$this->filter = array('fields' => $fields, 'value' => $value, 'mode' => $mode, 'ids' => array());
// search be iterating over all records in memory
foreach ($this->contacts as $id => $contact) {
// check if current contact has required values, otherwise skip it
if ($required) {
foreach ($required as $f)
if (empty($contact[$f]))
continue 2;
}
$found = array();
foreach (preg_grep($regexp, array_keys($contact)) as $col) {
if ($advanced) {
$pos = strpos($col, ':');
$colname = $pos ? substr($col, 0, $pos) : $col;
$search = $value[array_search($colname, $fields)];
}
else {
$search = $value;
}
foreach ((array)$contact[$col] as $val) {
$val = mb_strtolower($val);
switch ($mode) {
case 1:
$got = ($val == $search);
break;
case 2:
$got = ($search == substr($val, 0, strlen($search)));
break;
default:
$got = (strpos($val, $search) !== false);
break;
}
if ($got) {
if (!$advanced) {
$this->filter['ids'][] = $id;
break 2;
}
else {
$found[$colname] = true;
}
}
}
}
if (count($found) >= $scount) // && $advanced
$this->filter['ids'][] = $id;
}
// list records (now limited by $this->filter)
return $this->list_records();
}
/**
* Refresh saved search results after data has changed
*/
public function refresh_search()
{
if ($this->filter)
$this->search($this->filter['fields'], $this->filter['value'], $this->filter['mode']);
return $this->get_search_set();
}
/**
* Count number of available contacts in database
*
* @return rcube_result_set Result set with values for 'count' and 'first'
*/
public function count()
{
if ($this->gid) {
$this->_fetch_groups();
$count = count($this->distlists[$this->gid]['member']);
}
else if (is_array($this->filter['ids'])) {
$count = count($this->filter['ids']);
}
else {
$count = $this->storagefolder->count();
}
return new rcube_result_set($count, ($this->list_page-1) * $this->page_size);
}
/**
* Return the last result set
*
* @return rcube_result_set Current result set or NULL if nothing selected yet
*/
public function get_result()
{
return $this->result;
}
/**
* Get a specific contact record
*
* @param mixed record identifier(s)
* @param boolean True to return record as associative array, otherwise a result set is returned
* @return mixed Result object with all record fields or False if not found
*/
public function get_record($id, $assoc=false)
{
if ($object = $this->storagefolder->get_object($this->_id2uid($id))) {
$rec = $this->_to_rcube_contact($object);
$this->result = new rcube_result_set(1);
$this->result->add($rec);
return $assoc ? $rec : $this->result;
}
return false;
}
/**
* Get group assignments of a specific contact record
*
* @param mixed Record identifier
* @return array List of assigned groups as ID=>Name pairs
*/
public function get_record_groups($id)
{
$out = array();
$this->_fetch_groups();
foreach ((array)$this->groupmembers[$id] as $gid) {
if ($group = $this->distlists[$gid])
$out[$gid] = $group['name'];
}
return $out;
}
/**
* Create a new contact record
*
* @param array Assoziative array with save data
* Keys: Field name with optional section in the form FIELD:SECTION
* Values: Field value. Can be either a string or an array of strings for multiple values
* @param boolean True to check for duplicates first
* @return mixed The created record ID on success, False on error
*/
public function insert($save_data, $check=false)
{
if (!is_array($save_data))
return false;
$insert_id = $existing = false;
// check for existing records by e-mail comparison
if ($check) {
foreach ($this->get_col_values('email', $save_data, true) as $email) {
if (($res = $this->search('email', $email, true, false)) && $res->count) {
$existing = true;
break;
}
}
}
if (!$existing) {
// generate new Kolab contact item
$object = $this->_from_rcube_contact($save_data);
$object['uid'] = kolab_format::generate_uid();
$saved = $this->storagefolder->save($object, 'contact');
if (PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving contact object to Kolab server:" . $saved->getMessage()),
true, false);
}
else {
$contact = $this->_to_rcube_contact($object);
$id = $contact['ID'];
$this->contacts[$id] = $contact;
$insert_id = $id;
}
}
return $insert_id;
}
/**
* Update a specific contact record
*
* @param mixed Record identifier
* @param array Assoziative array with save data
* Keys: Field name with optional section in the form FIELD:SECTION
* Values: Field value. Can be either a string or an array of strings for multiple values
* @return boolean True on success, False on error
*/
public function update($id, $save_data)
{
$updated = false;
if ($old = $this->storagefolder->get_object($this->_id2uid($id))) {
$object = array_merge($old, $this->_from_rcube_contact($save_data));
$saved = $this->storagefolder->save($object, 'contact', $uid);
if (!$saved || PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving contact object to Kolab server"),
true, false);
}
else {
$this->contacts[$id] = $this->_to_rcube_contact($object);
$updated = true;
}
}
return $updated;
}
/**
* Mark one or more contact records as deleted
*
* @param array Record identifiers
* @param boolean Remove record(s) irreversible (mark as deleted otherwise)
*
* @return int Number of records deleted
*/
public function delete($ids, $force=true)
{
$this->_fetch_groups();
if (!is_array($ids))
$ids = explode(',', $ids);
$count = 0;
foreach ($ids as $id) {
if ($uid = $this->_id2uid($id)) {
$deleted = $this->storagefolder->delete($uid, $force);
if (!$deleted) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting a contact object from the Kolab server"),
true, false);
}
else {
// remove from distribution lists
foreach ((array)$this->groupmembers[$id] as $gid)
$this->remove_from_group($gid, $id);
// clear internal cache
unset($this->contacts[$id], $this->groupmembers[$id]);
$count++;
}
}
}
return $count;
}
/**
* Undelete one or more contact records.
* Only possible just after delete (see 2nd argument of delete() method).
*
* @param array Record identifiers
*
* @return int Number of records restored
*/
public function undelete($ids)
{
if (!is_array($ids))
$ids = explode(',', $ids);
$count = 0;
$uids = array();
/*
TODO: re-implement this using kolab_storage_folder::undelete();
$imap_uids = $_SESSION['kolab_delete_uids'];
// convert contact IDs into IMAP UIDs
foreach ($ids as $id)
if ($uid = $imap_uids[$id])
$uids[] = $uid;
if (!empty($uids)) {
$session = &Horde_Kolab_Session::singleton();
$imap = &$session->getImap();
if (is_object($imap) && is_a($imap, 'PEAR_Error')) {
$error = $imap;
}
else {
$result = $imap->select($this->imap_folder);
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
$result = $imap->undeleteMessages(implode(',', $uids));
if (is_object($result) && is_a($result, 'PEAR_Error')) {
$error = $result;
}
else {
$this->_connect();
$this->storagefolder->synchronize();
}
}
}
if ($error) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error undeleting a contact object(s) from the Kolab server:" . $error->getMessage()),
true, false);
}
$rcmail = rcmail::get_instance();
$rcmail->session->remove('kolab_delete_uids');
}
*/
return count($uids);
}
/**
* Remove all records from the database
*/
public function delete_all()
{
if ($this->storagefolder->delete_all()) {
$this->contacts = array();
$this->result = null;
}
}
/**
* Close connection to source
* Called on script shutdown
*/
public function close()
{
}
/**
* Create a contact group with the given name
*
* @param string The group name
* @return mixed False on error, array with record props in success
*/
function create_group($name)
{
$this->_fetch_groups();
$result = false;
$list = array(
'uid' => kolab_format::generate_uid(),
'name' => $name,
'member' => array(),
);
$saved = $this->storagefolder->save($list, 'distribution-list');
if (PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
true, false);
return false;
}
else {
$id = md5($list['uid']);
$this->distlists[$id] = $list;
$result = array('id' => $id, 'name' => $name);
}
return $result;
}
/**
* Delete the given group and all linked group members
*
* @param string Group identifier
* @return boolean True on success, false if no data was changed
*/
function delete_group($gid)
{
$this->_fetch_groups();
$result = false;
if ($list = $this->distlists[$gid])
$deleted = $this->storagefolder->delete($list['uid']);
if (PEAR::isError($deleted)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting distribution-list object from the Kolab server:" . $deleted->getMessage()),
true, false);
}
else
$result = true;
return $result;
}
/**
* Rename a specific contact group
*
* @param string Group identifier
* @param string New name to set for this group
* @return boolean New name on success, false if no data was changed
*/
function rename_group($gid, $newname)
{
$this->_fetch_groups();
$list = $this->distlists[$gid];
if ($newname != $list['name']) {
$list['name'] = $newname;
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
}
if (PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
true, false);
return false;
}
return $newname;
}
/**
* Add the given contact records the a certain group
*
* @param string Group identifier
* @param array List of contact identifiers to be added
* @return int Number of contacts added
*/
function add_to_group($gid, $ids)
{
if (!is_array($ids))
$ids = explode(',', $ids);
$added = 0;
$exists = array();
$this->_fetch_groups();
$this->_fetch_contacts();
$list = $this->distlists[$gid];
foreach ((array)$list['member'] as $i => $member)
$exists[] = $member['ID'];
// substract existing assignments from list
$ids = array_diff($ids, $exists);
foreach ($ids as $contact_id) {
if ($uid = $this->_id2uid($contact_id)) {
$contact = $this->contacts[$contact_id];
foreach ($this->get_col_values('email', $contact, true) as $email) {
$list['member'][] = array(
'uid' => $uid,
'display-name' => $contact['name'],
'smtp-address' => $email,
);
}
$this->groupmembers[$contact_id][] = $gid;
$added++;
}
}
if ($added)
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
if (PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list to Kolab server:" . $saved->getMessage()),
true, false);
$added = false;
}
else {
$this->distlists[$gid] = $list;
}
return $added;
}
/**
* Remove the given contact records from a certain group
*
* @param string Group identifier
* @param array List of contact identifiers to be removed
* @return int Number of deleted group members
*/
function remove_from_group($gid, $ids)
{
if (!is_array($ids))
$ids = explode(',', $ids);
$this->_fetch_groups();
if (!($list = $this->distlists[$gid]))
return false;
$new_member = array();
foreach ((array)$list['member'] as $member) {
if (!in_array($member['ID'], $ids))
$new_member[] = $member;
}
// write distribution list back to server
$list['member'] = $new_member;
$saved = $this->storagefolder->save($list, 'distribution-list', $list['uid']);
if (PEAR::isError($saved)) {
raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to Kolab server:" . $saved->getMessage()),
true, false);
}
else {
// remove group assigments in local cache
foreach ($ids as $id) {
$j = array_search($gid, $this->groupmembers[$id]);
unset($this->groupmembers[$id][$j]);
}
$this->distlists[$gid] = $list;
return true;
}
return false;
}
/**
* Check the given data before saving.
* If input not valid, the message to display can be fetched using get_error()
*
* @param array Associative array with contact data to save
*
* @return boolean True if input is valid, False if not.
*/
public function validate($save_data)
{
// validate e-mail addresses
$valid = parent::validate($save_data);
// require at least one e-mail address (syntax check is already done)
if ($valid) {
if (!strlen($save_data['name'])
&& !array_filter($this->get_col_values('email', $save_data, true))
) {
$this->set_error('warning', 'kolab_addressbook.noemailnamewarning');
$valid = false;
}
}
return $valid;
}
/**
* Simply fetch all records and store them in private member vars
*/
private function _fetch_contacts()
{
if (!isset($this->contacts)) {
$this->contacts = array();
foreach ((array)$this->storagefolder->get_objects() as $record) {
// Because of a bug, sometimes group records are returned
if ($record['__type'] == 'Group')
continue;
$contact = $this->_to_rcube_contact($record);
$id = $contact['ID'];
$this->contacts[$id] = $contact;
}
}
}
/**
* Callback function for sorting contacts
*/
private function _sort_contacts_comp($a, $b)
{
$a_value = $b_value = '';
switch ($this->sort_col) {
case 'name':
$a_value = $a['name'] . $a['prefix'];
$b_value = $b['name'] . $b['prefix'];
case 'firstname':
$a_value .= $a['firstname'] . $a['middlename'] . $a['surname'];
$b_value .= $b['firstname'] . $b['middlename'] . $b['surname'];
break;
case 'surname':
$a_value = $a['surname'] . $a['firstname'] . $a['middlename'];
$b_value = $b['surname'] . $b['firstname'] . $b['middlename'];
break;
default:
$a_value = $a[$this->sort_col];
$b_value = $b[$this->sort_col];
break;
}
$a_value .= is_array($a['email']) ? $a['email'][0] : $a['email'];
$b_value .= is_array($b['email']) ? $b['email'][0] : $b['email'];
// return strcasecmp($a_value, $b_value);
// make sorting unicode-safe and locale-dependent
if ($a_value == $b_value)
return 0;
$arr = array($a_value, $b_value);
sort($arr, SORT_LOCALE_STRING);
return $a_value == $arr[0] ? -1 : 1;
}
/**
* Read distribution-lists AKA groups from server
*/
private function _fetch_groups()
{
if (!isset($this->distlists)) {
$this->distlists = $this->groupmembers = array();
foreach ((array)$this->storagefolder->get_objects('distribution-list') as $record) {
$record['ID'] = $this->_uid2id($record['uid']);
foreach ((array)$record['member'] as $i => $member) {
$mid = $this->_uid2id($member['uid']);
$record['member'][$i]['ID'] = $mid;
$this->groupmembers[$mid][] = $record['ID'];
}
$this->distlists[$record['ID']] = $record;
}
}
}
/**
* Encode object UID into a safe identifier
*/
private function _uid2id($uid)
{
return rtrim(strtr(base64_encode($uid), '+/', '-_'), '=');
}
/**
* Convert Roundcube object identifier back into the original UID
*/
private function _id2uid($id)
{
return base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT));
}
/**
* Map fields from internal Kolab_Format to Roundcube contact format
*/
private function _to_rcube_contact($record)
{
$record['ID'] = $this->_uid2id($record['uid']);
if (is_array($record['phone'])) {
$phones = $record['phone'];
unset($record['phone']);
foreach ((array)$phones as $i => $phone) {
$key = 'phone' . ($phone['type'] ? ':' . $phone['type'] : '');
$record[$key][] = $phone['number'];
}
}
if (is_array($record['address'])) {
$addresses = $record['address'];
unset($record['address']);
foreach ($addresses as $i => $adr) {
$key = 'address' . ($adr['type'] ? ':' . $adr['type'] : '');
$record[$key][] = array(
'street' => $adr['street'],
'locality' => $adr['locality'],
'zipcode' => $adr['code'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
}
// photo is stored as separate attachment
- if ($record['photo'] && ($att = $record['_attachments'][$record['photo']])) {
- $out['photo'] = $att['content'] ? $att['content'] : $this->storagefolder->get_attachment($record['uid'], $att['key']);
+ if ($record['photo'] && strlen($record['photo']) < 255 && ($att = $record['_attachments'][$record['photo']])) {
+ // only fetch photo content if requested
+ if ($rcmail = rcmail::get_instance()->action == 'photo')
+ $record['photo'] = $att['content'] ? $att['content'] : $this->storagefolder->get_attachment($record['uid'], $att['key']);
}
// remove empty fields
return array_filter($record);
}
/**
* Map fields from Roundcube format to internal Kolab_Format
*/
private function _from_rcube_contact($contact)
{
if (!$contact['uid'] && $contact['ID'])
$contact['uid'] = $this->_id2uid($contact['ID']);
/*
// format dates
if ($object['birthday'] && ($date = @strtotime($object['birthday'])))
$object['birthday'] = date('Y-m-d', $date);
if ($object['anniversary'] && ($date = @strtotime($object['anniversary'])))
$object['anniversary'] = date('Y-m-d', $date);
*/
$contact['email'] = array_filter($this->get_col_values('email', $contact, true));
$contact['website'] = array_filter($this->get_col_values('website', $contact, true));
$contact['im'] = array_filter($this->get_col_values('im', $contact, true));
foreach ($this->get_col_values('phone', $contact) as $type => $values) {
foreach ((array)$values as $phone) {
if (!empty($phone)) {
$contact['phone'][] = array('number' => $phone, 'type' => $type);
}
}
}
$addresses = array();
foreach ($this->get_col_values('address', $contact) as $type => $values) {
foreach ((array)$values as $adr) {
// skip empty address
$adr = array_filter($adr);
if (empty($adr))
continue;
$addresses[] = array(
'type' => $type,
'street' => $adr['street'],
'locality' => $adr['locality'],
'code' => $adr['zipcode'],
'region' => $adr['region'],
'country' => $adr['country'],
);
}
unset($contact['address:'.$type]);
}
$contact['address'] = $addresses;
// save new photo as attachment
if ($contact['photo']) {
$attkey = 'photo.attachment';
$contact['_attachments'][$attkey] = array(
'type' => rc_image_content_type($contact['photo']),
'content' => preg_match('![^a-z0-9/=+-]!i', $contact['photo']) ? $contact['photo'] : base64_decode($contact['photo']),
);
$contact['photo'] = $attkey;
}
+ else if (isset($contact['photo']) && empty($contact['photo'])) {
+ // unset photo attachment
+ $contact['_attachments']['photo.attachment'] = false;
+ }
return $contact;
}
}
diff --git a/plugins/libkolab/lib/kolab_format.php b/plugins/libkolab/lib/kolab_format.php
index 928ee2f6..bcad59e7 100644
--- a/plugins/libkolab/lib/kolab_format.php
+++ b/plugins/libkolab/lib/kolab_format.php
@@ -1,147 +1,182 @@
<?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
+ * Convert the given date/time value into a cDateTime 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
+ * @return object The libkolabxml date/time object or null on error
*/
- public static function getDateTime($datetime, $tz = null, $dateonly = false)
+ public static function get_datetime($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))
+ else if (is_string($datetime) && strlen($datetime))
$datetime = new DateTime($datetime, $tz);
if (is_a($datetime, 'DateTime')) {
- $result = new KolabDateTime();
+ $result = new cDateTime();
$result->setDate($datetime->format('Y'), $datetime->format('n'), $datetime->format('j'));
if (!$dateonly)
$result->setTime($datetime->format('G'), $datetime->format('i'), $datetime->format('s'));
if ($tz)
$result->setTimezone($tz->getName());
}
return $result;
}
+ /**
+ * Convert the given cDateTime into a PHP DateTime object
+ *
+ * @param object cDateTime The libkolabxml datetime object
+ * @return object DateTime PHP datetime instance
+ */
+ public static function php_datetime($cdt)
+ {
+ if (!is_object($cdt) || !$cdt->isValid())
+ return null;
+
+ $d = new DateTime;
+ $d->setTimezone(self::$timezone);
+
+ try {
+ if ($tzs = $cdt->timezone()) {
+ $tz = new DateTimeZone($tzs);
+ $d->setTimezone($tz);
+ }
+ }
+ catch (Exception $e) { }
+
+ $d->setDate($cdt->year(), $cdt->month(), $cdt->day());
+
+ if ($cdt->isDateOnly()) {
+ $d->_dateonly = true;
+ $d->setTime(12, 0, 0); // set time to noon to avoid timezone troubles
+ }
+ else {
+ $d->setTime($cdt->hour(), $cdt->minute(), $cdt->second());
+ }
+
+ return $d;
+ }
+
/**
* Convert a libkolabxml vector to a PHP array
*
* @param object vector Object
* @return array Indexed array contaning vector elements
*/
public static function vector2array($vec, $max = PHP_INT_MAX)
{
$arr = array();
for ($i=0; $i < $vec->size() && $i < $max; $i++)
$arr[] = $vec->get($i);
return $arr;
}
/**
* Build a libkolabxml vector (string) from a PHP array
*
* @param array Array with vector elements
* @return object vectors
*/
public static function array2vector($arr)
{
$vec = new vectors;
foreach ((array)$arr as $val)
$vec->push($val);
return $vec;
}
/**
* 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();
}
diff --git a/plugins/libkolab/lib/kolab_format_contact.php b/plugins/libkolab/lib/kolab_format_contact.php
index 4dbcaf8f..8ff60879 100644
--- a/plugins/libkolab/lib/kolab_format_contact.php
+++ b/plugins/libkolab/lib/kolab_format_contact.php
@@ -1,338 +1,340 @@
<?php
class kolab_format_contact extends kolab_format
{
public $CTYPE = 'application/vcard+xml';
public $phonetypes = array(
'home' => Telephone::Home,
'work' => Telephone::Work,
'text' => Telephone::Text,
'main' => Telephone::Voice,
'homefax' => Telephone::Fax,
'workfax' => Telephone::Fax,
'mobile' => Telephone::Cell,
'video' => Telephone::Video,
'pager' => Telephone::Pager,
'car' => Telephone::Car,
'other' => Telephone::Textphone,
);
public $addresstypes = array(
'home' => Address::Home,
'work' => Address::Work,
);
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',
+ 'picture' => 'photo',
);
private $kolab2_phonetypes = array(
'home1' => 'home',
'business1' => 'work',
'business2' => 'work',
'businessfax' => 'workfax',
);
private $kolab2_addresstypes = array(
'business' => 'work'
);
private $kolab2_gender = array(0 => 'male', 1 => 'female');
/**
* Default constructor
*/
function __construct()
{
$this->obj = new Contact;
// complete phone types
$this->phonetypes['homefax'] |= Telephone::Home;
$this->phonetypes['workfax'] |= Telephone::Work;
}
/**
* 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']));
+ $this->obj->setCreated(self::get_datetime($object['created']));
}
// do the hard work of setting object values
$this->obj->setUid($object['uid']);
$nc = new NameComponents;
$nc->setSurnames(self::array2vector($object['surname']));
$nc->setGiven(self::array2vector($object['firstname']));
$nc->setAdditional(self::array2vector($object['middlename']));
$nc->setPrefixes(self::array2vector($object['prefix']));
$nc->setSuffixes(self::array2vector($object['suffix']));
$this->obj->setNameComponents($nc);
$this->obj->setName($object['name']);
if ($object['nickname'])
$this->obj->setNickNames(self::array2vector($object['nickname']));
// organisation related properties (affiliation)
$org = new Affiliation;
if ($object['organization'])
$org->setOrganisation($object['organization']);
if ($object['jobtitle'])
$org->setTitles(self::array2vector($object['jobtitle']));
if ($object['officelocation'])
$org->setOffices(self::array2vector($object['officelocation']));
if ($object['manager'])
$org->setManagers(self::array2vector($object['manager']));
if ($object['assistant'])
$org->setAssistants(self::array2vector($object['assistant']));
// department ?
$orgs = new vectoraffiliation;
$orgs->push($org);
$this->obj->setAffiliations($orgs);
// email, im, url
$this->obj->setEmailAddresses(self::array2vector($object['email']));
$this->obj->setIMaddresses(self::array2vector($object['im']));
$this->obj->setUrls(self::array2vector($object['website']));
// addresses
$adrs = new vectoraddress;
foreach ($object['address'] as $address) {
$adr = new Address;
$type = $this->addresstypes[$address['type']];
if (isset($type))
$adr->setTypes($type);
else if ($address['type'])
$adr->setLabel($address['type']);
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);
// telephones
$tels = new vectortelephone;
foreach ((array)$object['phone'] as $phone) {
$tel = new Telephone;
if (isset($this->phonetypes[$phone['type']]))
$tel->setTypes($this->phonetypes[$phone['type']]);
$tel->setNumber($phone['number']);
$tels->push($tel);
}
$this->obj->setTelephones($tels);
if ($object['gender'])
$this->obj->setGender($object['gender'] == 'female' ? Contact::Female : Contact::Male);
if ($object['notes'])
$this->obj->setNote($object['notes']);
if ($object['freebusyurl'])
$this->obj->setFreeBusyUrl($object['freebusyurl']);
-// if ($object['birthday'])
-// $this->obj->setBDay(self::getDateTime($object['birthday'], null, true));
-// if ($object['anniversary'])
-// $this->obj->setAnniversary(self::getDateTime($object['anniversary'], null, true));
+ if ($object['birthday'])
+ $this->obj->setBDay(self::get_datetime($object['birthday'], null, true));
+ if ($object['anniversary'])
+ $this->obj->setAnniversary(self::get_datetime($object['anniversary'], null, true));
// handle spouse, children, profession, initials, pgppublickey, etc.
// 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;
// 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['nickname'] = join(' ', self::vector2array($this->obj->nickNames()));
// organisation related properties (affiliation)
$orgs = $this->obj->affiliations();
if ($orgs->size()) {
$org = $orgs->get(0);
$object['organization'] = $org->organisation();
$object['jobtitle'] = join(' ', self::vector2array($org->titles()));
$object['manager'] = join(' ', self::vector2array($org->managers()));
$object['assistant'] = join(' ', self::vector2array($org->assistants()));
$object['officelocation'] = join(' ', self::vector2array($org->offices()));
}
$object['email'] = self::vector2array($this->obj->emailAddresses());
$object['im'] = self::vector2array($this->obj->imAddresses());
$object['website'] = self::vector2array($this->obj->urls());
// addresses
$adrtypes = array_flip($this->addresstypes);
$addresses = $this->obj->addresses();
for ($i=0; $i < $addresses->size(); $i++) {
$adr = $addresses->get($i);
$object['address'][] = array(
'type' => $adrtypes[$adr->types()] ? $adrtypes[$adr->types()] : '', /*$adr->label(),*/
'street' => $adr->street(),
'code' => $adr->code(),
'locality' => $adr->locality(),
'region' => $adr->region(),
'country' => $adr->country()
);
}
// telehones
$tels = $this->obj->telephones();
$teltypes = array_flip($this->phonetypes);
for ($i=0; $i < $tels->size(); $i++) {
$tel = $tels->get($i);
$object['phone'][] = array('number' => $tel->number(), 'type' => $teltypes[$tel->types()]);
}
$object['notes'] = $this->obj->note();
$object['freebusyurl'] = $this->obj->freeBusyUrl();
-
+
+ if ($bday = self::php_datetime($this->obj->bDay()))
+ $object['birthday'] = $bday->format('c');
+
+ if ($anniversary = self::php_datetime($this->obj->anniversary()))
+ $object['anniversary'] = $anniversary->format('c');
+
if ($g = $this->obj->gender())
$object['gender'] = $g == Contact::Female ? 'female' : 'male';
$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->kolab2_gender[$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' => $this->kolab2_addresstypes[$adr['type']] ? $this->kolab2_addresstypes[$adr['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);
}
}
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index d82c5c69..c47d41c5 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -1,528 +1,566 @@
<?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
*/
public function get_acl()
{
return join('', (array)$this->imap->get_acl($this->name));
}
/**
* Get number of objects stored in this folder
*
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
* @return integer The number of objects of the given type
*/
public function count($type = null)
{
if (!$type) $type = $this->type;
// search by object type
$ctype = self::KTYPE_PREFIX . $type;
$index = $this->imap->search_once($this->name, 'HEADER X-Kolab-Type ' . $ctype);
return $index->count();
}
/**
* 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)
*/
public 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
+ * @param string Object's UID
+ * @param string The attachment's mime number
+ * @param string IMAP folder where message is stored;
+ * If set, that also implies that the given UID is an IMAP UID
+ * @return mixed The attachment content as binary string
*/
- public function get_attachment($uid, $key)
+ public function get_attachment($uid, $part, $mailbox = null)
{
- // TODO: implement this
+ if ($msguid = ($mailbox ? $uid : $this->uid2msguid($uid))) {
+ if ($mailbox)
+ $this->imap->set_folder($mailbox);
- if ($msguid = $this->uid2msguid($uid)) {
- $message = new rcube_message($msguid);
+ return $this->imap->get_message_part($msguid, $part);
}
return null;
}
/**
+ * Fetch the mime message from the storage server and extract
+ * the Kolab groupware object from it
*
+ * @param string The IMAP message UID to fetch
+ * @param string The object type expected
+ * @return array Hash array representing the Kolab object
*/
- private function read_object($msguid, $type = null)
+ private function read_object($msguid, $type = null, $folder = null)
{
if (!$type) $type = $this->type;
+ if (!$folder) $folder = $this->name;
$ctype= self::KTYPE_PREFIX . $type;
- $this->imap->set_folder($this->name);
+ $this->imap->set_folder($folder);
$message = new rcube_message($msguid);
+ $attachments = array();
// get XML part
foreach ((array)$message->attachments as $part) {
- if ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype)) {
+ if (!$xml && ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
- break;
+ }
+ else if ($part->filename) {
+ $attachments[$part->filename] = array(
+ 'key' => $part->mime_id,
+ 'type' => $part->mimetype,
+ 'size' => $part->size,
+ );
}
}
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;
+ $object['_attachments'] = $attachments;
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;
+ // copy attachments from old message
+ if (!empty($object['_msguid']) && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox']))) {
+ foreach ($old['_attachments'] as $name => $att) {
+ if (!isset($object['_attachments'][$name])) {
+ $object['_attachments'][$name] = $old['_attachments'][$name];
+ }
+ }
+ }
+
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
$this->uid2msg[$object['uid']] = $result;
}
return $result;
}
/**
* Delete the specified object from this folder.
*
* @param mixed $object The Kolab object to delete or object UID
* @param boolean $trigger Should the folder be triggered?
* @param boolean $expunge Should the folder be expunged?
*
* @return boolean True if successful, false on error
*/
public function delete($object, $expunge = true, $trigger = true)
{
if ($msguid = is_array($object) ? $object['_msguid'] : $this->uid2msguid($object)) {
return $this->imap->delete_message($msguid, $this->name);
}
return false;
}
/**
*
*/
public function delete_all()
{
return $this->imap->clear_folder($this->name);
}
/**
* Restore a previously deleted object
*
* @param string Object UID
* @return mixed Message UID on success, false on error
*/
public function undelete($uid)
{
// TODO: implement this
return false;
}
/**
* 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
);
+ // save object attachments as separate parts
+ // TODO: optimize memory consumption by using tempfiles for transfer
+ foreach ((array)$object['_attachments'] as $name => $att) {
+ if (empty($att['content']) && !empty($att['key'])) {
+ $msguid = !empty($object['_msguid']) ? $object['_msguid'] : $object['uid'];
+ $att['content'] = $this->get_attachment($msguid, $att['key'], $object['_mailbox']);
+ }
+ if (!empty($att['content'])) {
+ $mime->addAttachment($att['content'], $att['type'], $name, false);
+ }
+ }
+
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();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Aug 17, 8:24 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1261600
Default Alt Text
(73 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment