Contact groups

This commit is contained in:
Aleksander Machniak 2022-11-10 13:38:48 +01:00
parent b6cc8c0715
commit 5fbfa61a4b
4 changed files with 164 additions and 47 deletions

View file

@ -32,7 +32,7 @@ class carddav_contacts extends rcube_addressbook
public $rights = 'lrs';
public $readonly = true;
public $undelete = false;
public $groups = false; // TODO
public $groups = true;
public $coltypes = [
'name' => ['limit' => 1],
@ -108,7 +108,7 @@ class carddav_contacts extends rcube_addressbook
'manager',
'assistant',
'spouse',
'children',
// 'children',
'notes',
];
@ -810,30 +810,34 @@ class carddav_contacts extends rcube_addressbook
function create_group($name)
{
$this->_fetch_groups();
$result = false;
$rcube = rcube::get_instance();
$list = [
'name' => $name,
'uid' => strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($rcube->user->get_username()), 0, 16)),
'name' => $name,
'kind' => 'group',
'member' => [],
];
$saved = $this->storage->save($list, 'distribution-list');
$saved = $this->storage->save($list, 'contact');
if (!$saved) {
rcube::raise_error([
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to CardDAV server"
'message' => "Error saving a contact group to CardDAV server"
],
true, false
);
return false;
}
else {
$id = $this->uid2id($list['uid']);
$this->distlists[$id] = $list;
$result = ['id' => $id, 'name' => $name];
}
return $result;
$id = $this->uid2id($list['uid']);
$this->distlists[$id] = $list;
return ['id' => $id, 'name' => $name];
}
/**
@ -846,25 +850,25 @@ class carddav_contacts extends rcube_addressbook
function delete_group($gid)
{
$this->_fetch_groups();
$result = false;
if ($list = $this->distlists[$gid]) {
$deleted = $this->storage->delete($list['uid']);
$list = $this->distlists[$gid];
if (!$list) {
return false;
}
$deleted = $this->storage->delete($list['uid']);
if (!$deleted) {
rcube::raise_error([
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Error deleting distribution-list object from the CardDAV server"
'message' => "Error deleting a contact group from the CardDAV server"
],
true, false
);
}
else {
$result = true;
}
return $result;
return $deleted;
}
/**
@ -874,25 +878,33 @@ class carddav_contacts extends rcube_addressbook
* @param string New name to set for this group
* @param string New group identifier (if changed, otherwise don't set)
*
* @return bool New name on success, false if no data was changed
* @return string|false New name on success, false if no data was changed
*/
function rename_group($gid, $newname, &$newid)
{
$this->_fetch_groups();
$list = $this->distlists[$gid];
if ($newname != $list['name']) {
$list['name'] = $newname;
$saved = $this->storage->save($list, 'distribution-list', $list['uid']);
if (!$list) {
return false;
}
if ($newname === $list['name']) {
return $newname;
}
$list['name'] = $newname;
$saved = $this->storage->save($list, 'contact', $list['uid']);
if (!$saved) {
rcube::raise_error([
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to CardDAV server"
'message' => "Error saving a contact group to CardDAV server"
],
true, false
);
return false;
}
@ -904,7 +916,8 @@ class carddav_contacts extends rcube_addressbook
*
* @param string Group identifier
* @param array List of contact identifiers to be added
* @return int Number of contacts added
*
* @return int Number of contacts added
*/
function add_to_group($gid, $ids)
{
@ -914,12 +927,17 @@ class carddav_contacts extends rcube_addressbook
$this->_fetch_groups(true);
$list = $this->distlists[$gid];
$list = $this->distlists[$gid];
if (!$list) {
return 0;
}
$added = 0;
$uids = [];
$exists = [];
foreach ((array)$list['member'] as $member) {
foreach ((array) $list['member'] as $member) {
$exists[] = $member['ID'];
}
@ -952,7 +970,7 @@ class carddav_contacts extends rcube_addressbook
}
if ($added) {
$saved = $this->storage->save($list, 'distribution-list', $list['uid']);
$saved = $this->storage->save($list, 'contact', $list['uid']);
}
else {
$saved = true;
@ -961,7 +979,7 @@ class carddav_contacts extends rcube_addressbook
if (!$saved) {
rcube::raise_error([
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list to CardDAV server"
'message' => "Error saving a contact-group to CardDAV server"
],
true, false
);
@ -986,13 +1004,16 @@ class carddav_contacts extends rcube_addressbook
*/
function remove_from_group($gid, $ids)
{
if (!is_array($ids)) {
$ids = explode(',', $ids);
$this->_fetch_groups();
$list = $this->distlists[$gid];
if (!$list) {
return false;
}
$this->_fetch_groups();
if (!($list = $this->distlists[$gid])) {
return false;
if (!is_array($ids)) {
$ids = explode(',', $ids);
}
$new_member = [];
@ -1004,12 +1025,12 @@ class carddav_contacts extends rcube_addressbook
// write distribution list back to server
$list['member'] = $new_member;
$saved = $this->storage->save($list, 'distribution-list', $list['uid']);
$saved = $this->storage->save($list, 'contact', $list['uid']);
if (!$saved) {
rcube::raise_error([
'code' => 600, 'file' => __FILE__, 'line' => __LINE__,
'message' => "Error saving distribution-list object to CardDAV server"
'message' => "Error saving a contact group to CardDAV server"
],
true, false
);
@ -1020,7 +1041,9 @@ class carddav_contacts extends rcube_addressbook
$j = array_search($gid, $this->groupmembers[$id]);
unset($this->groupmembers[$id][$j]);
}
$this->distlists[$gid] = $list;
return true;
}
@ -1130,15 +1153,17 @@ class carddav_contacts extends rcube_addressbook
}
/**
* Read distribution-lists AKA groups from server
* Read contact groups from server
*/
private function _fetch_groups($with_contacts = false)
{
return; // TODO
if (!isset($this->distlists)) {
$this->distlists = $this->groupmembers = [];
foreach ($this->storage->select('distribution-list', true) as $record) {
// Set order (and LIMIT to skip the count(*) select)
$this->storage->set_order_and_limit(['name'], 200, 0);
foreach ($this->storage->select('group', true) as $record) {
$record['ID'] = $this->uid2id($record['uid']);
foreach ((array)$record['member'] as $i => $member) {
$mid = $this->uid2id($member['uid'] ? $member['uid'] : 'mailto:' . $member['email']);
@ -1150,8 +1175,11 @@ class carddav_contacts extends rcube_addressbook
$this->contacts[$mid] = $record['member'][$i];
}
}
$this->distlists[$record['ID']] = $record;
}
$this->storage->set_order_and_limit($this->_sort_columns(), null, 0);
}
}

View file

@ -55,7 +55,7 @@ class kolab_storage_dav
/**
* Get a list of storage folders for the given data type
*
* @param string Data type to list folders for (contact,distribution-list,event,task,note)
* @param string Data type to list folders for (contact,event,task,note)
*
* @return array List of kolab_storage_dav_folder objects
*/
@ -87,7 +87,7 @@ class kolab_storage_dav
/**
* Getter for the storage folder for the given type
*
* @param string Data type to list folders for (contact,distribution-list,event,task,note)
* @param string Data type to list folders for (contact,event,task,note)
*
* @return object kolab_storage_dav_folder The folder object
*/

View file

@ -38,6 +38,10 @@ class kolab_storage_dav_cache_contact extends kolab_storage_dav_cache
$sql_data = parent::_serialize($object);
$sql_data['type'] = $object['_type'] ?: 'contact';
if ($sql_data['type'] == 'group' || (!empty($object['kind']) && $object['kind'] == 'group')) {
$sql_data['type'] = 'group';
}
// columns for sorting
$sql_data['name'] = rcube_charset::clean($object['name'] . $object['prefix']);
$sql_data['firstname'] = rcube_charset::clean($object['firstname'] . $object['middlename'] . $object['surname']);

View file

@ -483,10 +483,52 @@ class kolab_storage_dav_folder extends kolab_storage_folder
return false;
}
$vcard = new rcube_vcard($object['data'], RCUBE_CHARSET, false);
// vCard properties not supported by rcube_vcard
$map = [
'uid' => 'UID',
'kind' => 'KIND',
'member' => 'MEMBER',
'x-kind' => 'X-ADDRESSBOOKSERVER-KIND',
'x-member' => 'X-ADDRESSBOOKSERVER-MEMBER',
];
// TODO: We should probably use Sabre/Vobject to parse the vCard
$vcard = new rcube_vcard($object['data'], RCUBE_CHARSET, false, $map);
if (!empty($vcard->displayname) || !empty($vcard->surname) || !empty($vcard->firstname) || !empty($vcard->email)) {
$result = $vcard->get_assoc();
// Contact groups
if (!empty($result['x-kind']) && implode($result['x-kind']) == 'group') {
$result['_type'] = 'group';
$members = isset($result['x-member']) ? $result['x-member'] : [];
unset($result['x-kind'], $result['x-member']);
}
else if (!empty($result['kind']) && implode($result['kind']) == 'group') {
$result['_type'] = 'group';
$members = isset($result['member']) ? $result['member'] : [];
unset($result['kind'], $result['member']);
}
if (isset($members)) {
$result['member'] = [];
foreach ($members as $member) {
if (strpos($member, 'urn:uuid:') === 0) {
$result['member'][] = ['uid' => substr($member, 9)];
}
else if (strpos($member, 'mailto:') === 0) {
$member = reset(rcube_mime::decode_address_list(urldecode(substr($member, 7))));
if (!empty($member['mailto'])) {
$result['member'][] = ['email' => $member['mailto'], 'name' => $member['name']];
}
}
}
}
if (!empty($result['uid'])) {
$result['uid'] = preg_replace('/^urn:uuid:/', '', implode($result['uid']));
}
}
else {
return false;
@ -517,9 +559,17 @@ class kolab_storage_dav_folder extends kolab_storage_folder
}
else if ($this->type == 'contact') {
// copy values into vcard object
$vcard = new rcube_vcard('', RCUBE_CHARSET, false, ['uid' => 'UID']);
// TODO: We should probably use Sabre/Vobject to create the vCard
$vcard->set('groups', null);
// vCard properties not supported by rcube_vcard
$map = ['uid' => 'UID', 'kind' => 'KIND'];
$vcard = new rcube_vcard('', RCUBE_CHARSET, false, $map);
if ((!empty($object['_type']) && $object['_type'] == 'group')
|| (!empty($object['type']) && $object['type'] == 'group')
) {
$object['kind'] = 'group';
}
foreach ($object as $key => $values) {
list($field, $section) = rcube_utils::explode(':', $key);
@ -537,6 +587,41 @@ class kolab_storage_dav_folder extends kolab_storage_folder
}
$result = $vcard->export(false);
if (!empty($object['kind']) && $object['kind'] == 'group') {
$members = '';
foreach ((array) $object['member'] as $member) {
$value = null;
if (!empty($member['uid'])) {
$value = 'urn:uuid:' . $member['uid'];
}
else if (!empty($member['email']) && !empty($member['name'])) {
$value = 'mailto:' . urlencode(sprintf('"%s" <%s>', addcslashes($member['name'], '"'), $member['email']));
}
else if (!empty($member['email'])) {
$value = 'mailto:' . $member['email'];
}
if ($value) {
$members .= "MEMBER:{$value}\r\n";
}
}
if ($members) {
$result = preg_replace('/\r\nEND:VCARD/', "\r\n{$members}END:VCARD", $result);
}
/**
Version 4.0 of the vCard format requires Cyrus >= 3.6.0, we'll use Version 3.0 for now
$result = preg_replace('/\r\nVERSION:3\.0\r\n/', "\r\nVERSION:4.0\r\n", $result);
$result = preg_replace('/\r\nN:[^\r]+/', '', $result);
$result = preg_replace('/\r\nUID:([^\r]+)/', "\r\nUID:urn:uuid:\\1", $result);
*/
$result = preg_replace('/\r\nMEMBER:([^\r]+)/', "\r\nX-ADDRESSBOOKSERVER-MEMBER:\\1", $result);
$result = preg_replace('/\r\nKIND:([^\r]+)/', "\r\nX-ADDRESSBOOKSERVER-KIND:\\1", $result);
}
}
if ($result) {
@ -578,7 +663,7 @@ class kolab_storage_dav_folder extends kolab_storage_folder
/**
* Return folder name as string representation of this object
*
* @return string Full IMAP folder name
* @return string Folder display name
*/
public function __toString()
{