first try to export distribution lists via CardDAV as vCard with "X-CALENDARSERVER-KIND:group", we might need a user-agent whitelist, as not all clients will understand that

This commit is contained in:
Ralf Becker 2012-01-31 09:57:59 +00:00
parent 2a7d39d3cf
commit 41eaebde79
5 changed files with 312 additions and 78 deletions

View File

@ -7,7 +7,7 @@
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de>
* @package addressbook
* @copyright (c) 2005-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-12 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
@ -1727,38 +1727,40 @@ class addressbook_bo extends addressbook_so
}
/**
* Adds a distribution list
* Adds / updates a distribution list
*
* @param string $name list-name
* @param string|array $keys list-name or array with column-name => value pairs to specify the list
* @param int $owner user- or group-id
* @param array $contacts=array() contacts to add
* @return list_id or false on error
* @param array $contacts=array() contacts to add (only for not yet existing lists!)
* @param array &$data=array() values for keys 'list_uid', 'list_carddav_name', 'list_name'
* @return int|boolean integer list_id or false on error
*/
function add_list($name,$owner,$contacts=array())
function add_list($keys,$owner,$contacts=array(),array &$data=array())
{
if (!$this->check_list(null,EGW_ACL_ADD,$owner)) return false;
return parent::add_list($name,$owner,$contacts);
return parent::add_list($name,$owner,$contacts,$data);
}
/**
* Adds one contact to a distribution list
* Adds contacts to a distribution list
*
* @param int $contact contact_id
* @param int|array $contact contact_id(s)
* @param int $list list-id
* @param array $existing=null array of existing contact-id(s) of list, to not reread it, eg. array()
* @return false on error
*/
function add2list($contact,$list)
function add2list($contact,$list,array $existing=null)
{
if (!$this->check_list($list,EGW_ACL_EDIT)) return false;
return parent::add2list($contact,$list);
return parent::add2list($contact,$list,$existing);
}
/**
* Removes one contact from distribution list(s)
*
* @param int $contact contact_id
* @param int|array $contact contact_id(s)
* @param int $list list-id
* @return false on error
*/

View File

@ -42,7 +42,7 @@ class addressbook_groupdav extends groupdav_handler
'ADR;HOME' => array('','adr_two_street2','adr_two_street','adr_two_locality','adr_two_region',
'adr_two_postalcode','adr_two_countryname'),
'BDAY' => array('bday'),
//'CLASS' => array('private'),
//'CLASS' => array('private'),
//'CATEGORIES' => array('cat_id'),
'EMAIL;WORK' => array('email'),
'EMAIL;HOME' => array('email_home'),
@ -70,6 +70,7 @@ class addressbook_groupdav extends groupdav_handler
'X-ASSISTANT' => array('assistent'),
'X-ASSISTANT-TEL' => array('tel_assistent'),
'UID' => array('uid'),
'REV' => array('modified'),
);
/**
@ -203,6 +204,31 @@ class addressbook_groupdav extends groupdav_handler
$files[] = $this->add_resource($path, $contact, $props);
}
}
// add groups after contacts
if (!$start || count($contacts) < $start[1])
{
if (($lists = $this->bo->read_lists(array('list_owner' => $filter['contact_owner']?$filter['contact_owner']:array_keys($this->bo->grants)))))
{
//_debug_array($lists);
foreach($lists as $list)
{
$list['carddav_name'] = $list['list_carddav_name'];
$props = array(
'getcontenttype' => HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/vcard'),
'getlastmodified' => egw_time::to($list['list_modified'],'ts'),
'displayname' => $list['list_name'],
'getetag' => '"'.$list['list_id'].':'.$list['list_etag'].'"',
);
if ($address_data)
{
$content = $handler->getGroupVCard($list);
$props['getcontentlength'] = bytes($content);
$props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content,true);
}
$files[] = $this->add_resource($path, $list, $props);
}
}
}
if ($this->debug) error_log(__METHOD__."($path,".array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items');
return $files;
}
@ -386,7 +412,8 @@ class addressbook_groupdav extends groupdav_handler
return $contact;
}
$handler = self::_get_handler();
$options['data'] = $handler->getVCard($contact['id'],$this->charset,false);
$options['data'] = $contact['list_id'] ? $handler->getGroupVCard($contact) :
$handler->getVCard($contact['id'],$this->charset,false);
// e.g. Evolution does not understand 'text/vcard'
$options['mimetype'] = 'text/x-vcard; charset='.$this->charset;
header('Content-Encoding: identity');
@ -450,8 +477,13 @@ class addressbook_groupdav extends groupdav_handler
$contactId = -1;
$retval = '201 Created';
}
$is_group = $contact['##X-CALENDARSERVER-KIND'] == 'group';
if ($oldContact && $is_group !== isset($oldContact['list_id']))
{
throw new egw_exception_assertion_failed(__METHOD__."(,'$id',$user,'$prefix') can contact into group or visa-versa!");
}
if (is_array($contact['cat_id']))
if (!$is_group && is_array($contact['cat_id']))
{
$contact['cat_id'] = implode(',',$this->bo->find_or_add_categories($contact['cat_id'], $contactId));
}
@ -486,7 +518,7 @@ class addressbook_groupdav extends groupdav_handler
}
if ($this->http_if_match) $contact['etag'] = self::etag2value($this->http_if_match);
if (!($save_ok = $this->bo->save($contact)))
if (!($save_ok = $is_group ? $this->save_group($contact) : $this->bo->save($contact)))
{
if ($this->debug) error_log(__METHOD__."(,$id) save(".array2string($contact).") failed, Ok=$save_ok");
if ($save_ok === 0)
@ -514,6 +546,69 @@ class addressbook_groupdav extends groupdav_handler
return $retval;
}
/**
* Save distribition-list / group
*
* @param array $contact
* @param array|false $oldContact
* @param int|boolean $list_id or false on error
*/
function save_group(array $contact, $oldContact=null)
{
$data = array('list_name' => $contact['n_fn']);
foreach(array('id','carddav_name','uid') as $name)
{
if ($name != self::$path_attr) $data['list_'.$name] = $contact[$name];
}
if (($list_id=$this->bo->add_list(array('list_'.self::$path_attr => $contact[self::$path_attr]),
$contact['owner'], null, $data)))
{
// update members given in $contact['##X-CALENDARSERVER-MEMBER']
$new_members = $contact['##X-CALENDARSERVER-MEMBER'];
if ($new_members[1] == ':' && ($n = unserialize($new_members)))
{
$new_members = $n['values'];
}
else
{
$new_members = array($new_members);
}
foreach($new_members as &$uid) $uid = substr($uid,9); // cut off "urn:uuid:" prefix
if ($oldContact)
{
$to_add = array_diff($oldContact['members'],$new_members);
$to_delete = array_diff($new_members,$oldContact['members']);
}
else
{
$to_add = $new_members;
}
if ($to_add || $to_delete)
{
$to_add_ids = $to_delete_ids = array();
$filter = array('uid' => $to_delete ? array_merge($to_add, $to_delete) : $to_add);
if (($contacts =& $this->bo->search(array(),'id,uid','','','',False,'AND',false,$filter)))
{
foreach($contacts as $contact)
{
if ($to_delete && in_array($contact['uid'], $to_delete))
{
$to_delete_ids[] = $contact['id'];
}
else
{
$to_add_ids[] = $contact['id'];
}
}
}
if ($to_add_ids) $this->bo->add2list($to_add_ids, $list_id, array());
if ($to_delete_ids) $this->bo->remove_from_list($to_delete_ids, $list_id);
}
}
return $list_id;
}
/**
* Query ctag for addressbook
*
@ -661,6 +756,17 @@ class addressbook_groupdav extends groupdav_handler
}
$contact = $this->bo->read(array(self::$path_attr => $id, 'tid' => $non_deleted_tids));
// see if we have a distribution-list / group with that id
if (!$contact && ($contact = $this->bo->read_lists(array('list_'.self::$path_attr => $id))))
{
$contact = array_shift($contact);
$contact['n_fn'] = $contact['n_family'] = $contact['list_name'];
foreach(array('owner','id','carddav_name','modified','modifier','created','creator') as $name)
{
$contact[$name] = $contact['list_'.$name];
}
}
if ($contact && $contact['tid'] == addressbook_so::DELETED_TYPE)
{
$contact = null; // handle deleted events, as not existing (404 Not Found)

View File

@ -6,7 +6,7 @@
* @author Cornelius Weiss <egw-AT-von-und-zu-weiss.de>
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package addressbook
* @copyright (c) 2005-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005-12 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2005/6 by Cornelius Weiss <egw@von-und-zu-weiss.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
@ -961,38 +961,54 @@ class addressbook_so
}
/**
* Adds a distribution list
* Get the availible distribution lists for givens users and groups
*
* @param string $name list-name
* @param int $owner user- or group-id
* @param array $contacts=array() contacts to add
* @return list_id or false on error
* @param array $keys column-name => value(s) pairs, eg. array('list_uid'=>$uid)
* @param string $member_attr='contact_uid' null: no members, 'contact_uid', 'contact_id', 'caldav_name' return members as that attribute
* @return array with list_id => array(list_id,list_name,list_owner,...,'members') pairs
*/
function add_list($name,$owner,$contacts=array())
function read_lists(array $keys,$member_attr='contact_uid')
{
if (!method_exists($this->somain,'add_list')) return false;
if (!method_exists($this->somain,'get_lists')) return false;
return $this->somain->add_list($name,$owner,$contacts);
return $this->somain->get_lists($keys,null,$member_attr);
}
/**
* Adds one contact to a distribution list
* Adds / updates a distribution list
*
* @param int $contact contact_id
* @param string|array $keys list-name or array with column-name => value pairs to specify the list
* @param int $owner user- or group-id
* @param array $contacts=array() contacts to add (only for not yet existing lists!)
* @param array &$data=array() values for keys 'list_uid', 'list_carddav_name', 'list_name'
* @return int|boolean integer list_id or false on error
*/
function add_list($keys,$owner,$contacts=array(),array &$data=array())
{
if (!method_exists($this->somain,'add_list')) return false;
return $this->somain->add_list($name,$owner,$contacts,$data);
}
/**
* Adds contact(s) to a distribution list
*
* @param int|array $contact contact_id(s)
* @param int $list list-id
* @param array $existing=null array of existing contact-id(s) of list, to not reread it, eg. array()
* @return false on error
*/
function add2list($contact,$list)
function add2list($contact,$list,array $existing=null)
{
if (!method_exists($this->somain,'add2list')) return false;
return $this->somain->add2list($contact,$list);
return $this->somain->add2list($contact,$list,$existing);
}
/**
* Removes one contact from distribution list(s)
*
* @param int $contact contact_id
* @param int|array $contact contact_id(s)
* @param int $list=null list-id or null to remove from all lists
* @return false on error
*/

View File

@ -5,7 +5,7 @@
* @link http://www.egroupware.org
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @package addressbook
* @copyright (c) 2006-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2006-12 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
@ -458,101 +458,162 @@ class addressbook_sql extends so_sql_cf
/**
* Get the availible distribution lists for givens users and groups
*
* @param array $uids user or group id's
* @param array $uids array of user or group id's for $uid_column='list_owners', or values for $uid_column,
* or whole where array: column-name => value(s) pairs
* @param string $uid_column='list_owner' column-name or null to use $uids as where array
* @param string $member_attr=null null: no members, 'contact_uid', 'contact_id', 'caldav_name' return members as that attribute
* @return array with list_id => array(list_id,list_name,list_owner,...) pairs
*/
function get_lists($uids)
function get_lists($uids,$uid_column='list_owner',$member_attr=null)
{
$user = $GLOBALS['egw_info']['user']['account_id'];
$lists = array();
foreach($this->db->select($this->lists_table,'*',array('list_owner'=>$uids),__LINE__,__FILE__,
foreach($this->db->select($this->lists_table,'*',$uid_column?array($uid_column=>$uids):$uids,__LINE__,__FILE__,
false,'ORDER BY list_owner<>'.(int)$GLOBALS['egw_info']['user']['account_id'].',list_name') as $row)
{
$lists[$row['list_id']] = $row;
}
//echo "<p>socontacts_sql::get_lists(".print_r($uids,true).")</p>\n"; _debug_array($lists);
if ($lists && $member_attr && in_array($member_attr,array('contact_id','contact_uid','caldav_name')))
{
foreach($this->db->select($this->ab2list_table,"list_id,$member_attr",array('list_id'=>array_keys($lists)),
__LINE__,__FILE__,false,$member_attr=='contact_id' ? '' :
'',false,0,"JOIN $this->table_name ON $this->ab2list_table.contact_id=$this->table_name.contact_id") as $row)
{
$lists[$row['list_id']]['members'][] = $row[$member_attr];
}
}
error_log(__METHOD__.'('.array2string($uids).", '$uid_column', '$member_attr') returning ".array2string($lists));
return $lists;
}
/**
* Adds a distribution list
* Adds / updates a distribution list
*
* @param string $name list-name
* @param string|array $keys list-name or array with column-name => value pairs to specify the list
* @param int $owner user- or group-id
* @param array $contacts=array() contacts to add
* @return int/boolean integer list_id, true if the list already exists or false on error
* @param array $contacts=array() contacts to add (only for not yet existing lists!)
* @param array &$data=array() values for keys 'list_uid', 'list_carddav_name', 'list_name'
* @return int|boolean integer list_id or false on error
*/
function add_list($name,$owner,$contacts=array())
function add_list($keys,$owner,$contacts=array(),array &$data=array())
{
if (!$name || !(int)$owner) return false;
if (!$keys || !(int)$owner) return false;
if ($this->db->select($this->lists_table,'list_id',array(
'list_name' => $name,
'list_owner' => $owner,
),__LINE__,__FILE__)->fetchColumn())
if (!is_array($keys)) $keys = array('list_name' => $keys);
$keys['list_owner'] = $owner;
if (!($list_id = $this->select($this->lists_table,'list_id',$keys)->fetchColumn()))
{
return true; // return existing list-id
$data['list_created'] = time();
$data['list_creator'] = $GLOBALS['egw_info']['user']['account_id'];
}
if (!$this->db->insert($this->lists_table,array(
'list_name' => $name,
'list_owner' => $owner,
'list_created' => time(),
'list_creator' => $GLOBALS['egw_info']['user']['account_id'],
),array(),__LINE__,__FILE__)) return false;
if ((int)($list_id = $this->db->get_last_insert_id($this->lists_table,'list_id')) && $contacts)
else
{
foreach($contacts as $contact)
$data[] = 'list_etag=list_etag+1';
}
$data['list_modified'] = time();
$data['list_modifier'] = $GLOBALS['egw_info']['user']['account_id'];
if (!$this->db->insert($this->lists_table,$data,$keys,__LINE__,__FILE__)) return false;
if (!$list_id && ($list_id = $this->db->get_last_insert_id($this->lists_table,'list_id')) &&
(!isset($data['list_uid']) || !isset($data['list_carddav_name'])))
{
$update = array();
if (!isset($data['list_uid']))
{
$this->add2list($list_id,$contact);
$update['list_uid'] = $data['list_uid'] = common::generate_uid('addresbook-lists', $list_idD);
}
if (!isset($data['list_carddav_name']))
{
$update['list_carddav_name'] = $data['list_carddav_name'] = $data['list_uid'].'.vcf';
}
$this->db->update($this->lists_table,$update,array('list_id'=>$list_id));
$this->add2list($list_id,$contacts,array());
}
$data += $keys;
return $list_id;
}
/**
* Adds one contact to a distribution list
* Adds contact(s) to a distribution list
*
* @param int $contact contact_id
* @param int|array $contact contact_id(s)
* @param int $list list-id
* @param array $existing=null array of existing contact-id(s) of list, to not reread it, eg. array()
* @return false on error
*/
function add2list($contact,$list)
function add2list($contact,$list,array $existing=null)
{
if (!(int)$list || !(int)$contact) return false;
if (!(int)$list || !is_array($contact) && !(int)$contact) return false;
if ($this->db->select($this->ab2list_table,'list_id',array(
'contact_id' => $contact,
'list_id' => $list,
),__LINE__,__FILE__)->fetchColumn())
if (!is_array($existing)) $existing = $this->read_list($list);
if (!($to_add = array_diff((array)$contact,$existing)))
{
return true; // no need to insert it, would give sql error
}
return $this->db->insert($this->ab2list_table,array(
'contact_id' => $contact,
foreach($to_add as $contact)
{
$this->db->insert($this->ab2list_table,array(
'contact_id' => $contact,
'list_id' => $list,
'list_added' => time(),
'list_added_by' => $GLOBALS['egw_info']['user']['account_id'],
),array(),__LINE__,__FILE__);
}
// update etag
return $this->db->update($this->list_table,array(
'list_etag=list_etag+1',
'list_modified' => time(),
'list_modifier' => $GLOBALS['egw_info']['user']['account_id'],
),array(
'list_id' => $list,
'list_added' => time(),
'list_added_by' => $GLOBALS['egw_info']['user']['account_id'],
),array(),__LINE__,__FILE__);
),__LINE__,__FILE__);
}
/**
* Removes one contact from distribution list(s)
*
* @param int $contact contact_id
* @param int|array $contact contact_id(s)
* @param int $list=null list-id or null to remove from all lists
* @return false on error
*/
function remove_from_list($contact,$list=null)
{
if (!(int)$list && !is_null($list) || !(int)$contact) return false;
if (!(int)$list && !is_null($list) || !is_array($contact) && !(int)$contact) return false;
$where = array(
'contact_id' => $contact,
);
if (!is_null($list)) $where['list_id'] = $list;
return $this->db->delete($this->ab2list_table,$where,__LINE__,__FILE__);
if (!is_null($list))
{
$where['list_id'] = $list;
}
else
{
$list = array();
foreach($this->db->select($this->ab2list_table,'list_id',$where,__LINE__,__FILE__) as $row)
{
$list[] = $row['list_id'];
}
}
if (!$this->db->delete($this->ab2list_table,$where,__LINE__,__FILE__))
{
return false;
}
foreach((array)$list as $list_id)
{
$this->db->update($this->list_table,array(
'list_etag=list_etag+1',
'list_modified' => time(),
'list_modifier' => $GLOBALS['egw_info']['user']['account_id'],
),array(
'list_id' => $list_id,
),__LINE__,__FILE__);
}
return true;
}
/**

View File

@ -72,6 +72,7 @@ class addressbook_vcal extends addressbook_bo
'X-ASSISTANT' => array('assistent'),
'X-ASSISTANT-TEL' => array('tel_assistent'),
'UID' => array('uid'),
'REV' => array('modified'),
);
/**
@ -206,7 +207,7 @@ class addressbook_vcal extends addressbook_bo
$vCard->setAttribute('PRODID','-//EGroupware//NONSGML EGroupware Addressbook '.$GLOBALS['egw_info']['apps']['addressbook']['version'].'//'.
strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang']));
$sysCharSet = $GLOBALS['egw']->translation->charset();
$sysCharSet = translation::charset();
// KAddressbook and Funambol4BlackBerry always requires non-ascii chars to be qprint encoded.
if ($this->productName == 'kde' ||
@ -280,6 +281,10 @@ class addressbook_vcal extends addressbook_bo
switch ($databaseField)
{
case 'modified':
$value = gmdate("Y-m-d\TH:i:s\Z",egw_time::user2server($value));
break;
case 'private':
$value = $value ? 'PRIVATE' : 'PUBLIC';
$hasdata++;
@ -326,7 +331,7 @@ class addressbook_vcal extends addressbook_bo
case 'cat_id':
if (!empty($value) && ($values = $this->get_categories($value)))
{
$values = (array) $GLOBALS['egw']->translation->convert($values, $sysCharSet, $_charset);
$values = (array) translation::convert($values, $sysCharSet, $_charset);
$value = implode(',', $values); // just for the CHARSET recognition
if (($size > 0) && strlen($value) > $size)
{
@ -410,7 +415,7 @@ class addressbook_vcal extends addressbook_bo
|| in_array($vcardField,array('FN','ORG','N'))
|| ($size >= 0 && !$noTruncate))
{
$value = $GLOBALS['egw']->translation->convert(trim($value), $sysCharSet, $_charset);
$value = translation::convert(trim($value), $sysCharSet, $_charset);
$values[] = $value;
if (preg_match('/[^\x20-\x7F]/', $value))
{
@ -965,6 +970,21 @@ class addressbook_vcal extends addressbook_bo
}
}
}
// add unsupported attributes as with '##' prefix
else
{
$attribute = $vcardValues[$vcardKey];
// for attributes with multiple values in multiple lines, merge the values
if (isset($contact['##'.$attribute['name']]))
{
error_log(__METHOD__."() taskData['##$attribute[name]'] = ".array2string($contact['##'.$attribute['name']]));
$attribute['values'] = array_merge(
is_array($contact['##'.$attribute['name']]) ? $contact['##'.$attribute['name']]['values'] : (array)$contact['##'.$attribute['name']],
$attribute['values']);
}
$contact['##'.$attribute['name']] = $attribute['params'] || count($attribute['values']) > 1 ?
serialize($attribute) : $attribute['value'];
}
}
$this->fixup_contact($contact);
@ -1011,8 +1031,37 @@ class addressbook_vcal extends addressbook_bo
if (!$file)
{
$GLOBALS['egw']->common->egw_exit();
common::egw_exit();
}
return true;
}
/**
* return a groupVCard
*
* @param array $list values for 'list_uid', 'list_name', 'list_modified', 'members'
* @param string $version='3.0' vcard version
* @return string containing the vcard
*/
function getGroupVCard(array $list,$version='3.0')
{
require_once(EGW_SERVER_ROOT.'/phpgwapi/inc/horde/Horde/iCalendar/vcard.php');
$vCard = new Horde_iCalendar_vcard($version);
$vCard->setAttribute('PRODID','-//EGroupware//NONSGML EGroupware Addressbook '.$GLOBALS['egw_info']['apps']['addressbook']['version'].'//'.
strtoupper($GLOBALS['egw_info']['user']['preferences']['common']['lang']));
$vCard->setAttribute('N',$list['list_name']);
$vCard->setAttribute('FN',$list['list_name']);
$vCard->setAttribute('X-ADRESSBOOKSERVER-KIND','group');
foreach($list['members'] as $uid)
{
$vCard->setAttribute('X-ADRESSBOOKSERVER-MEMBER','urn:uuid:'.$uid);
}
$vCard->setAttribute('REV',egw_time::to($list['list_modified'],'Y-m-d\TH:i:s\Z'));
$vCard->setAttribute('UID',$list['list_uid']);
return $vCard->exportvCalendar();
}
}