Addressbook synchronization backport

This commit is contained in:
Jörg Lehrke 2010-02-25 16:21:49 +00:00
parent 151b32da10
commit aa779da763
6 changed files with 485 additions and 161 deletions

View File

@ -95,6 +95,13 @@ class addressbook_bo extends addressbook_so
var $business_contact_fields = array();
var $home_contact_fields = array();
/**
* Set Logging
*
* @var boolean
*/
var $log = false;
/**
* Number and message of last error or false if no error, atm. only used for saving
*
@ -205,6 +212,7 @@ class addressbook_bo extends addressbook_so
'modified' => lang('last modified'),
'modifier' => lang('last modified by'),
'jpegphoto' => lang('photo'),
'account_id' => lang('Account ID'),
);
$this->business_contact_fields = array(
'org_name' => lang('Company'),
@ -390,7 +398,7 @@ class addressbook_bo extends addressbook_so
*/
function set_all_fileas($fileas_type,$all=false,&$errors=null,$ignore_acl=false)
{
if ($type != '' && !in_array($type,$this->fileas_types))
if ($fileas_type != '' && !in_array($fileas_type, $this->fileas_types))
{
return false;
}
@ -441,6 +449,98 @@ class addressbook_bo extends addressbook_so
return $updated;
}
/**
* Cleanup all contacts db fields of all users (called by Admin >> Addressbook >> Site configuration (Admin only)
*
* Cleanup means to truncate all unnecessary chars like whitespaces or tabs,
* remove unneeded carriage returns or set empty fields to NULL
*
* @param int &$errors=null on return number of errors
* @return int|boolean number of contacts updated
*/
function set_all_cleanup(&$errors=null,$ignore_acl=false)
{
if ($ignore_acl)
{
unset($this->somain->grants); // to NOT limit search to contacts readable by current user
}
// fields that must not be touched
$fields_exclude = array(
'id' => true,
'tid' => true,
'owner' => true,
'private' => true,
'created' => true,
'creator' => true,
'modified' => true,
'modifier' => true,
'account_id' => true,
'etag' => true,
'uid' => true,
'freebusy_uri' => true,
'calendar_uri' => true,
'photo' => true,
);
// to be able to work on huge contact repositories we read the contacts in chunks of 100
for($n = $updated = $errors = 0; ($contacts = parent::search(array(),false,'','','',false,'OR',array($n*100,100))); ++$n)
{
foreach($contacts as $contact)
{
$fields_to_update = array();
foreach($contact as $field_name => $field_value)
{
if($fields_exclude[$field_name] === true) continue; // dont touch specified field
if (is_string($field_value) && $field_name != 'pubkey' && $field_name != 'jpegphoto')
{
// check if field has to be trimmed
if (strlen($field_value) != strlen(trim($field_value)))
{
$fields_to_update[$field_name] = $field_value = trim($field_value);
}
// check if field contains a carriage return - exclude notes
if ($field_name != 'note' && strpos($field_value,"\x0D\x0A") !== false)
{
$fields_to_update[$field_name] = $field_value = str_replace("\x0D\x0A"," ",$field_value);;
}
}
// check if a field contains an empty string
if (is_string($field_value) && strlen($field_value) == 0)
{
$fields_to_update[$field_name] = $field_value = null;
}
// check for valid birthday date
if ($field_name == 'bday' && $field_value != null &&
!preg_match('/^(18|19|20|21|22)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/',$field_value))
{
$fields_to_update[$field_name] = $field_value = null;
}
}
if(count($fields_to_update) > 0)
{
$contact_to_save = array(
'id' => $contact['id'],
'owner' => $contact['owner'],
'private' => $contact['private'],
'account_id' => $contact['account_id'],
'uid' => $contact['uid']) + $fields_to_update;
if ($this->save($contact_to_save,$ignore_acl))
{
$updated++;
}
else
{
$errors++;
}
}
}
}
return $updated;
}
/**
* get full name from the name-parts
@ -474,9 +574,9 @@ class addressbook_bo extends addressbook_so
function db2data($data)
{
// convert timestamps from server-time in the db to user-time
foreach($this->timestamps as $name)
foreach ($this->timestamps as $name)
{
if(isset($data[$name]))
if (isset($data[$name]))
{
$data[$name] += $this->tz_offset_s;
}
@ -520,9 +620,9 @@ class addressbook_bo extends addressbook_so
function data2db($data)
{
// convert timestamps from user-time to server-time in the db
foreach($this->timestamps as $name)
foreach ($this->timestamps as $name)
{
if(isset($data[$name]))
if (isset($data[$name]))
{
$data[$name] -= $this->tz_offset_s;
}
@ -1545,38 +1645,55 @@ class addressbook_bo extends addressbook_so
*
* @param array $contact the contact data we try to find
* @param boolean $relax=false if asked to relax, we only match against some key fields
* @return the contact_id of the matching entry or false (if none matches)
* @return array od matching contact_ids
*/
function find_contact($contact, $relax=false)
{
if (!empty($contact['uid']))
if ($this->log)
{
// Try the given UID first
Horde::logMessage('Addressbook find UID: '. $contact['uid'],
__FILE__, __LINE__, PEAR_LOG_DEBUG);
$criteria = array ('contact_uid' => $contact['uid']);
if (($found = parent::search($criteria))
&& ($uidmatch = array_shift($found)))
{
return $uidmatch['contact_id'];
}
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '('. ($relax ? 'RELAX': 'EXACT') . ')[ContactData]:'
. array2string($contact));
}
unset($contact['uid']);
$matchingContacts = array();
if ($contact['id'] && ($found = $this->read($contact['id'])))
{
// We only do a simple consistency check
Horde::logMessage('Addressbook find ID: '. $contact['id'],
__FILE__, __LINE__, PEAR_LOG_DEBUG);
if ((empty($found['n_family']) || $found['n_family'] == $contact['n_family'])
&& (empty($found['n_given']) || $found['n_given'] == $contact['n_given'])
&& (empty($found['org_name']) || $found['org_name'] == $contact['org_name']))
if ($this->log)
{
return $found['id'];
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[ContactID]: ' . $contact['id']);
}
// We only do a simple consistency check
if (!$relax || ((empty($found['n_family']) || $found['n_family'] == $contact['n_family'])
&& (empty($found['n_given']) || $found['n_given'] == $contact['n_given'])
&& (empty($found['org_name']) || $found['org_name'] == $contact['org_name'])))
{
return array($found['id']);
}
}
unset($contact['id']);
if (!$relax && !empty($contact['uid']))
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[ContactUID]: ' . $contact['uid']);
}
// Try the given UID first
$criteria = array ('contact_uid' => $contact['uid']);
if (($foundContacts = parent::search($criteria)))
{
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
return $matchingContacts;
}
unset($contact['uid']);
$columns_to_search = array('n_family', 'n_given', 'n_middle', 'n_prefix', 'n_suffix',
'bday', 'org_name', 'org_unit', 'title', 'role',
'email', 'email_home');
@ -1697,28 +1814,47 @@ class addressbook_bo extends addressbook_so
}
}
}
Horde::logMessage("Addressbook find step 1:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 1]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
// first try full match
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
// No need for more searches for relaxed matching
if (!$relax && !$result && !$this->all_empty($contact, $addr_one_fields)
if (!$relax || count($matchingContacts)) return $matchingContacts;
if (!$this->all_empty($contact, $addr_one_fields)
&& $this->all_empty($contact, $addr_two_fields))
{
// try given address and ignore the second one in EGW
$filter = array_diff($filter, $no_addr_two);
Horde::logMessage("Addressbook find step 2:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 2]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
else
{
@ -1743,16 +1879,25 @@ class addressbook_bo extends addressbook_so
$filter = $filter + $no_addr_one;
Horde::logMessage("Addressbook find step 3:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 3]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if (($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
}
}
elseif (!$relax && !$result)
{ // No more searches for relaxed matching, try again after address swap
else
{ // try again after address swap
$filter = $empty_columns;
@ -1791,15 +1936,26 @@ class addressbook_bo extends addressbook_so
$filter[] = "(" . $db_col . " IS NULL OR " . $db_col . " = '')";
}
}
Horde::logMessage("Addressbook find step 4:\n" . print_r($criteria, true) .
"filter:\n" . print_r($filter, true), __FILE__, __LINE__, PEAR_LOG_DEBUG);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[Addressbook FIND Step 4]: '
. 'FILTER:' . array2string($filter)
. 'CRITERIA' . array2string($criteria));
}
if(($foundContacts = parent::search($criteria, true, '', '', '', False, 'AND', false, $filter)))
{
$result = $foundContacts[0]['id'];
foreach ($foundContacts as $egwContact)
{
$matchingContacts[] = $egwContact['id'];
}
}
}
return $result;
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__
. '()[FOUND]:' . array2string($matchingContacts));
}
return $matchingContacts;
}
}

View File

@ -1,25 +1,28 @@
<?php
/**
* eGroupWare: GroupDAV access: addressbook handler
* EGroupware: GroupDAV access: addressbook handler
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package addressbook
* @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007/8 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$
*/
/**
* eGroupWare: GroupDAV access: addressbook handler
* EGroupware: GroupDAV access: addressbook handler
*
* Propfind now uses a groupdav_propfind_iterator with a callback to query huge addressbooks in chunk,
* without getting into problems with memory_limit.
*/
class addressbook_groupdav extends groupdav_handler
{
/**
* bo class of the application
*
* @var addressbook_vcal
* @var addressbook_bo
*/
var $bo;
@ -93,33 +96,53 @@ class addressbook_groupdav extends groupdav_handler
if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id) filter=".array2string($filter));
// check if we have to return the full calendar data or just the etag's
if (!($address_data = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props']))
if (!($filter['address_data'] = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props']))
{
foreach($options['props'] as $prop)
{
if ($prop['name'] == 'address-data')
{
$address_data = true;
$filter['address_data'] = true;
break;
}
}
}
if ($address_data)
// return iterator, calling ourself to return result in chunks
$files['files'] = new groupdav_propfind_iterator($this,$filter,$files['files']);
return true;
}
/**
* Callback for profind interator
*
* @param array $filter
* @param array|boolean $start=false false=return all or array(start,num)
* @return array with "files" array with values for keys path and props
*/
function &propfind_callback(array $filter,$start=false)
{
$starttime = microtime(true);
if (($address_data = $filter['address_data']))
{
$handler = self::_get_handler();
}
unset($filter['address_data']);
$files = array();
// we query etag and modified, as LDAP does not have the strong sql etag
if (($contacts =& $this->bo->search(array(),$address_data ? false : array('id','uid','etag','modified'),'contact_id','','',False,'AND',false,$filter)))
if (($contacts =& $this->bo->search(array(),$address_data ? false : array('id','uid','etag','modified'),'contact_id','','',False,'AND',$start,$filter)))
{
foreach($contacts as $contact)
foreach($contacts as &$contact)
{
$props = array(
$props = array(
HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($contact)),
HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/x-vcard'),
// getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set
HTTP_WebDAV_Server::mkprop('getlastmodified', $contact['modified']),
);
if ($address_data)
if ($address_data)
{
$content = $handler->getVCard($contact,$this->charset,false);
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content));
@ -129,13 +152,14 @@ class addressbook_groupdav extends groupdav_handler
{
$props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it
}
$files['files'][] = array(
$files[] = array(
'path' => self::get_path($contact),
'props' => $props,
);
}
}
return true;
if ($this->debug) error_log(__METHOD__.'('.array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items');
return $files;
}
/**
@ -224,7 +248,7 @@ class addressbook_groupdav extends groupdav_handler
return $contact;
}
$handler = self::_get_handler();
$options['data'] = $handler->getVCard($contact['id'],$this->charset);
$options['data'] = $handler->getVCard($contact['id'],$this->charset,false);
$options['mimetype'] = 'text/x-vcard; charset='.$this->charset;
header('Content-Encoding: identity');
header('ETag: '.$this->get_etag($contact));
@ -283,6 +307,59 @@ class addressbook_groupdav extends groupdav_handler
return true;
}
/**
* Query ctag for addressbook
*
* @return string
*/
public function getctag($path,$user)
{
$filter = array();
// show addressbook of a single user?
if ($user && $path != '/addressbook/') $filter['contact_owner'] = $user;
// should we hide the accounts addressbook
if ($GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) $filter['account_id'] = null;
$result = $this->bo->search(array(),'MAX(contact_modified) AS contact_modified','','','','','',$filter);
return '"'.$result[0]['modified'].'"';
}
/**
* Add extra properties for addressbook collections
*
* Example for supported-report-set syntax from Apples Calendarserver:
* <D:supported-report-set>
* <supported-report>
* <report>
* <addressbook-query xmlns='urn:ietf:params:xml:ns:carddav'/>
* </report>
* </supported-report>
* <supported-report>
* <report>
* <addressbook-multiget xmlns='urn:ietf:params:xml:ns:carddav'/>
* </report>
* </supported-report>
* </D:supported-report-set>
* @link http://www.mail-archive.com/calendarserver-users@lists.macosforge.org/msg01156.html
*
* @param array $props=array() regular props by the groupdav handler
* @return array
*/
static function extra_properties(array $props=array())
{
// supported reports (required property for CardDAV)
$props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array(
HTTP_WebDAV_Server::mkprop('supported-report',array(
HTTP_WebDAV_Server::mkprop('report',
HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-query','')))),
HTTP_WebDAV_Server::mkprop('supported-report',array(
HTTP_WebDAV_Server::mkprop('report',
HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-multiget','')))),
));
return $props;
}
/**
* Get the handler and set the supported fields
*
@ -290,7 +367,7 @@ class addressbook_groupdav extends groupdav_handler
*/
private function _get_handler()
{
$handler = new addressbook_vcal();
$handler = new addressbook_vcal('addressbook','text/vcard');
$handler->setSupportedFields('GroupDAV',$this->agent);
return $handler;

View File

@ -826,7 +826,7 @@ class addressbook_ldap
$entries = ldap_get_entries($this->ds, $result);
$this->total = $entries['count'];
foreach($entries as $i => $entry)
foreach ((array)$entries as $i => $entry)
{
if (!is_int($i)) continue; // eg. count

View File

@ -99,21 +99,26 @@ class addressbook_sif extends addressbook_bo
const xml_decl = '<?xml version="1.0" encoding="UTF-8"?>';
const SIF_decl = '<SIFVersion>1.1</SIFVersion>';
function startElement($_parser, $_tag, $_attributes) {
function startElement($_parser, $_tag, $_attributes)
{
}
function endElement($_parser, $_tag) {
if(!empty($this->sifMapping[$_tag])) {
function endElement($_parser, $_tag)
{
if(!empty($this->sifMapping[$_tag]))
{
$this->contact[$this->sifMapping[$_tag]] = trim($this->sifData);
}
unset($this->sifData);
}
function characterData($_parser, $_data) {
function characterData($_parser, $_data)
{
$this->sifData .= $_data;
}
function siftoegw($sifData, $_abID=null) {
function siftoegw($sifData, $_abID=null)
{
#$tmpfname = tempnam('/tmp/sync/contents','sifc_');
@ -129,23 +134,25 @@ class addressbook_sif extends addressbook_bo
xml_set_element_handler($this->xml_parser, "startElement", "endElement");
xml_set_character_data_handler($this->xml_parser, "characterData");
$this->strXmlData = xml_parse($this->xml_parser, $sifData);
if(!$this->strXmlData) {
if (!$this->strXmlData)
{
error_log(sprintf("XML error: %s at line %d",
xml_error_string(xml_get_error_code($this->xml_parser)),
xml_get_current_line_number($this->xml_parser)));
return false;
}
foreach($this->contact as $key => $value) {
foreach ($this->contact as $key => $value)
{
$value = preg_replace('/<\!\[CDATA\[(.+)\]\]>/Usim', '$1', $value);
$value = $GLOBALS['egw']->translation->convert($value, 'utf-8');
switch($key) {
switch ($key) {
case 'cat_id':
if(!empty($value))
if (!empty($value))
{
$categories1 = explode(',', $value);
$categories2 = explode(';', $value);
$categories = count($categories1) > count($categories2) ? $categories1 : $categories2;
$categories1 = explode(',', $value);
$categories2 = explode(';', $value);
$categories = count($categories1) > count($categories2) ? $categories1 : $categories2;
$finalContact[$key] = implode(',', $this->find_or_add_categories($categories, $_abID));
}
else
@ -159,7 +166,7 @@ class addressbook_sif extends addressbook_bo
break;
default:
$finalContact[$key] = $value;
$finalContact[$key] = str_replace("\r\n", "\n", $value);
break;
}
}
@ -173,18 +180,20 @@ class addressbook_sif extends addressbook_bo
* Search an exactly matching entry (used for slow sync)
*
* @param string $_sifdata
* @return boolean/int/string contact-id or false, if not found
* @return array of matching contact-ids
*/
function search($_sifdata, $contentID=null, $relax=false)
{
$result = false;
$result = array();
if($contact = $this->siftoegw($_sifdata, $contentID)) {
if ($contentID) {
$contact['contact_id'] = $contentID;
}
$result = $this->find_contact($contact, $relax);
}
if ($contact = $this->siftoegw($_sifdata, $contentID))
{
if ($contentID)
{
$contact['contact_id'] = $contentID;
}
$result = $this->find_contact($contact, $relax);
}
return $result;
}
@ -198,22 +207,44 @@ class addressbook_sif extends addressbook_bo
*/
function addSIF($_sifdata, $_abID=null, $merge=false)
{
#error_log('ABID: '.$_abID);
#error_log(base64_decode($_sifdata));
if(!$contact = $this->siftoegw($_sifdata, $_abID)) {
if (!$contact = $this->siftoegw($_sifdata, $_abID))
{
return false;
}
if($_abID) {
if ($_abID)
{
if (($old_contact = $this->read($_abID)))
{
if ($merge)
{
foreach ($contact as $key => $value)
{
if (!empty($old_contact[$key]))
{
$contact[$key] = $old_contact[$key];
}
}
}
else
{
if (isset($old_contact['account_id']))
{
$contact['account_id'] = $old_contact['account_id'];
}
}
}
// update entry
$contact['id'] = $_abID;
}
elseif (array_key_exists('filter_addressbook', $GLOBALS['egw_info']['user']['preferences']['syncml']))
{
$contact['owner'] = (int) $GLOBALS['egw_info']['user']['preferences']['syncml']['filter_addressbook'];
if ($contact['owner'] == -1)
{
$contact['owner'] = $GLOBALS['egw_info']['user']['account_primary_group'];
}
}
return $this->save($contact);
}
@ -230,9 +261,7 @@ class addressbook_sif extends addressbook_bo
$fields = array_unique(array_values($this->sifMapping));
sort($fields);
if(!($entry = $this->read($_id))) {
return false;
}
if (!($entry = $this->read($_id))) return false;
$sifContact = self::xml_decl . "\n<contact>" . self::SIF_decl;
@ -241,16 +270,16 @@ class addressbook_sif extends addressbook_bo
// fillup some defaults such as n_fn and n_fileas is needed
$this->fixup_contact($entry);
foreach($this->sifMapping as $sifField => $egwField)
foreach ($this->sifMapping as $sifField => $egwField)
{
if(empty($egwField)) continue;
if (empty($egwField)) continue;
#error_log("$sifField => $egwField");
#error_log('VALUE1: '.$entry[0][$egwField]);
$value = $GLOBALS['egw']->translation->convert($entry[$egwField], $sysCharSet, 'utf-8');
#error_log('VALUE2: '.$value);
switch($sifField)
switch ($sifField)
{
case 'Sensitivity':
$value = 2 * $value; // eGW private is 0 (public) or 1 (private)
@ -263,8 +292,8 @@ class addressbook_sif extends addressbook_bo
break;
case 'Categories':
if(!empty($value)) {
$value = implode(', ', $this->get_categories($value));
if (!empty($value)) {
$value = implode(", ", $this->get_categories($value));
$value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8');
} else {
break;

View File

@ -24,7 +24,7 @@
*
* The accounts can be stored in SQL or LDAP too (account_repository):
* If the account-repository is different from the contacts-repository, the filter all (no owner set)
* will only search the accounts and NOT the contacts! Only the filter accounts (owner=0) shows accounts.
* will only search the contacts and NOT the accounts! Only the filter accounts (owner=0) shows accounts.
*
* If sql-ldap is used as contact-storage (LDAP is managed from eGroupWare) the filter all, searches
* the accounts in the SQL contacts-table too. Change in made in LDAP, are not detected in that case!
@ -130,6 +130,7 @@ class addressbook_so
var $sql_cols_not_to_search = array(
'jpegphoto','owner','tid','private','id','cat_id','etag',
'modified','modifier','creator','created','tz','account_id',
'uid',
);
/**
* columns to search, if we search for a single pattern
@ -752,8 +753,8 @@ class addressbook_so
if ($owner === '') $owner = null;
if ($this->contact_repository != $this->account_repository && is_object($this->so_accounts) &&
(!is_null($owner) && !$owner || !is_null($contact_id) &&
($this->contact_repository == 'sql' && (!is_numeric($contact_id) && !is_array($contact_id) )||
(!is_null($owner) && !$owner || is_array($contact_id) && $contact_id['account_id'] || !is_null($contact_id) &&
($this->contact_repository == 'sql' && (!is_numeric($contact_id) && !is_array($contact_id)) ||
$this->contact_repository == 'ldap' && is_numeric($contact_id))))
{
return $this->so_accounts;

View File

@ -96,8 +96,12 @@ class addressbook_vcal extends addressbook_bo
function __construct($contact_app='addressbook', $_contentType='text/x-vcard', &$_clientProperties = array())
{
parent::__construct($contact_app);
if($this->log)$this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-vcard";
if($this->log)error_log(__LINE__.__METHOD__.__FILE__.array2string($_contentType)."\n",3,$this->logfile);
if ($this->log)
{
$this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-vcard";
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($_contentType)."\n",3,$this->logfile);
}
switch($_contentType)
{
case 'text/vcard':
@ -119,17 +123,16 @@ class addressbook_vcal extends addressbook_bo
*/
function addVCard($_vcard, $_abID=null, $merge=false)
{
if(!$contact = $this->vcardtoegw($_vcard, $_abID))
if (!$contact = $this->vcardtoegw($_vcard, $_abID))
{
return false;
}
if($_abID)
if ($_abID)
{
if ($merge)
if (($old_contact = $this->read($_abID)))
{
$old_contact = $this->read($_abID);
if ($old_contact)
if ($merge)
{
foreach ($contact as $key => $value)
{
@ -139,6 +142,13 @@ class addressbook_vcal extends addressbook_bo
}
}
}
else
{
if (isset($old_contact['account_id']))
{
$contact['account_id'] = $old_contact['account_id'];
}
}
}
// update entry
$contact['id'] = $_abID;
@ -146,6 +156,10 @@ class addressbook_vcal extends addressbook_bo
elseif (array_key_exists('filter_addressbook', $GLOBALS['egw_info']['user']['preferences']['syncml']))
{
$contact['owner'] = (int) $GLOBALS['egw_info']['user']['preferences']['syncml']['filter_addressbook'];
if ($contact['owner'] == -1)
{
$contact['owner'] = $GLOBALS['egw_info']['user']['account_primary_group'];
}
}
return $this->save($contact);
}
@ -215,6 +229,12 @@ class addressbook_vcal extends addressbook_bo
{
$size = $this->clientProperties[$vcardField]['Size'];
$noTruncate = $this->clientProperties[$vcardField]['NoTruncate'];
if ($this->log && $size > 0)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() $vcardField Size: $size, NoTruncate: " .
($noTruncate ? 'TRUE' : 'FALSE') . "\n",3,$this->logfile);
}
//Horde::logMessage("vCalAddressbook $vcardField Size: $size, NoTruncate: " .
// ($noTruncate ? 'TRUE' : 'FALSE'), __FILE__, __LINE__, PEAR_LOG_DEBUG);
}
@ -225,14 +245,14 @@ class addressbook_vcal extends addressbook_bo
}
foreach ($databaseFields as $databaseField)
{
$value = "";
$value = '';
if (!empty($databaseField))
{
$value = trim($entry[$databaseField]);
}
switch($databaseField)
switch ($databaseField)
{
case 'private':
$value = $value ? 'PRIVATE' : 'PUBLIC';
@ -282,21 +302,48 @@ class addressbook_vcal extends addressbook_bo
{
$values = (array) $GLOBALS['egw']->translation->convert($values, $sysCharSet, $_charset);
$value = implode(',', $values); // just for the CHARSET recognition
if ($extra_charset_attribute && preg_match('/([\177-\377])/', $value))
if (($size > 0) && strlen($value) > $size)
{
$options['CHARSET'] = $_charset;
// let us try with only the first category
$value = $values[0];
if (strlen($value) > $size)
{
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() $vcardField omitted due to maximum size $size\n",3,$this->logfile);
}
// Horde::logMessage("vCalAddressbook $vcardField omitted due to maximum size $size",
// __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
}
$values = array();
}
if (preg_match('/[^\x20-\x7F]/', $value))
{
if ($extra_charset_attribute || $this->productName == 'kde')
{
$options['CHARSET'] = $_charset;
}
// KAddressbook requires non-ascii chars to be qprint encoded, other clients eg. nokia phones have trouble with that
if ($this->productName == 'kde' ||
($this->productManufacturer == 'funambol' && $this->productName == 'blackberry plug-in'))
if ($this->productName == 'kde')
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}
else
elseif ($this->productManufacturer == 'funambol')
{
$options['ENCODING'] = 'FUNAMBOL-QP';
}
elseif (preg_match('/([\000-\012\015\016\020-\037\075])/', $value))
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}
elseif (!$extra_charset_attribute)
{
$options['ENCODING'] = '';
}
}
$hasdata++;
$hasdata++;
}
break;
@ -305,7 +352,11 @@ class addressbook_vcal extends addressbook_bo
{
if ($noTruncate)
{
error_log(__FILE__ . __LINE__ . __METHOD__ . " vCalAddressbook $vcardField omitted due to maximum size $size");
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() $vcardField omitted due to maximum size $size\n",3,$this->logfile);
}
// Horde::logMessage("vCalAddressbook $vcardField omitted due to maximum size $size",
// __FILE__, __LINE__, PEAR_LOG_WARNING);
continue;
@ -321,7 +372,11 @@ class addressbook_vcal extends addressbook_bo
{
$value = '';
}
error_log(__FILE__ . __LINE__ . __METHOD__ . " vCalAddressbook $vcardField truncated to maximum size $size");
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() $vcardField truncated to maximum size $size\n",3,$this->logfile);
}
//Horde::logMessage("vCalAddressbook $vcardField truncated to maximum size $size",
// __FILE__, __LINE__, PEAR_LOG_INFO);
}
@ -331,33 +386,29 @@ class addressbook_vcal extends addressbook_bo
{
$value = $GLOBALS['egw']->translation->convert(trim($value), $sysCharSet, $_charset);
$values[] = $value;
if ($extra_charset_attribute)
if (preg_match('/[^\x20-\x7F]/', $value))
{
if (preg_match('/([\177-\377])/', $value))
if ($extra_charset_attribute || $this->productName == 'kde')
{
$options['CHARSET'] = $_charset;
// KAddressbook requires non-ascii chars to be qprint encoded, other clients eg. nokia phones have trouble with that
if ($this->productName == 'kde' ||
($this->productManufacturer == 'funambol' && $this->productName == 'blackberry plug-in'))
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}
else
{
$options['ENCODING'] = '';
}
}
// protect the CardDAV
if (preg_match('/([\000-\012\015\016\020-\037\075])/', $value))
// KAddressbook requires non-ascii chars to be qprint encoded, other clients eg. nokia phones have trouble with that
if ($this->productName == 'kde')
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}
}
else
{
// avoid that these options are inserted from horde code
$options['CHARSET'] = '';
$options['ENCODING'] = '';
elseif ($this->productManufacturer == 'funambol')
{
$options['ENCODING'] = 'FUNAMBOL-QP';
}
elseif (preg_match('/([\000-\012\015\016\020-\037\075])/', $value))
{
$options['ENCODING'] = 'QUOTED-PRINTABLE';
}
elseif (!$extra_charset_attribute)
{
$options['ENCODING'] = '';
}
}
if ($vcardField == 'TEL' && $entry['tel_prefer'] &&
($databaseField == $entry['tel_prefer']))
@ -392,15 +443,17 @@ class addressbook_vcal extends addressbook_bo
$result = $vCard->exportvCalendar();
if ($this->log)
{
error_log(__LINE__.__METHOD__.__FILE__."'$this->productManufacturer','$this->productName'"."\n",3,$this->logfile);
error_log(__LINE__.__METHOD__.__FILE__."\n".array2string($result)."\n",3,$this->logfile);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() '$this->productManufacturer','$this->productName'\n",3,$this->logfile);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($result)."\n",3,$this->logfile);
}
return $result;
}
function search($_vcard, $contentID=null, $relax=false)
{
$result = false;
$result = array();
if (($contact = $this->vcardtoegw($_vcard, $contentID)))
{
@ -465,7 +518,11 @@ class addressbook_vcal extends addressbook_bo
'UID' => array('uid'),
);
if ($this->log) error_log(__LINE__.__METHOD__.__FILE__."\n".array2string($_vcard)."\n",3,$this->logfile);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($_vcard)."\n",3,$this->logfile);
}
//Horde::logMessage("vCalAddressbook vcardtoegw:\n$_vcard", __FILE__, __LINE__, PEAR_LOG_DEBUG);
@ -614,24 +671,24 @@ class addressbook_vcal extends addressbook_bo
}
}
if($rowName == 'EMAIL')
if ($rowName == 'EMAIL')
{
$rowName .= ';X-egw-Ref' . $email++;
}
if(($rowName == 'TEL;CELL') ||
if (($rowName == 'TEL;CELL') ||
($rowName == 'TEL;CELL;VOICE'))
{
$rowName = 'TEL;CELL;X-egw-Ref' . $cell++;
}
if(($rowName == 'TEL') ||
if (($rowName == 'TEL') ||
($rowName == 'TEL;VOICE'))
{
$rowName = 'TEL;X-egw-Ref' . $tel++;
}
if($rowName == 'URL')
if ($rowName == 'URL')
{
$rowName = 'URL;X-egw-Ref' . $url++;
}
@ -639,23 +696,22 @@ class addressbook_vcal extends addressbook_bo
$rowNames[$key] = $rowName;
}
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($rowNames)."\n",3,$this->logfile);
}
if($this->log)error_log(__LINE__.__METHOD__.__FILE__."\n".array2string($rowNames)."\n",3,$this->logfile);
// All rowNames of the vCard are now concatenated with their qualifiers.
// If qualifiers are missing we apply a default strategy.
// E.g. ADR will be either ADR;WORK, if no ADR;WORK is given,
// or else ADR;HOME, if not available elsewhere.
//error_log(print_r($rowNames, true));
// All rowNames of the vCard are now concatenated with their qualifiers.
// If qualifiers are missing we apply a default strategy.
// E.g. ADR will be either ADR;WORK, if no ADR;WORK is given,
// or else ADR;HOME, if not available elsewhere.
$finalRowNames = array();
foreach ($rowNames as $vcardKey => $rowName)
{
switch($rowName)
switch ($rowName)
{
case 'VERSION':
break;
@ -796,10 +852,11 @@ class addressbook_vcal extends addressbook_bo
}
if($this->log)error_log(__LINE__.__METHOD__.__FILE__."\n".array2string($finalRowNames)."\n",3,$this->logfile);
//error_log(print_r($finalRowNames, true));
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($finalRowNames)."\n",3,$this->logfile);
}
$contact = array();
@ -840,8 +897,7 @@ class addressbook_vcal extends addressbook_bo
break;
case 'note':
// note may contain ','s but maybe this needs to be fixed in vcard parser...
$contact[$fieldName] = $vcardValues[$vcardKey]['value'];
$contact[$fieldName] = str_replace("\r\n", "\n", $vcardValues[$vcardKey]['value']);
break;
case 'uid':
@ -865,8 +921,13 @@ class addressbook_vcal extends addressbook_bo
$this->fixup_contact($contact);
if ($this->log) error_log(__LINE__.__METHOD__.__FILE__."'$this->productManufacturer','$this->productName'"."\n",3,$this->logfile);
if ($this->log) error_log(__LINE__.__METHOD__.__FILE__."\n".array2string($contact)."\n",3,$this->logfile);
if ($this->log)
{
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ .
"() '$this->productManufacturer','$this->productName'\n",3,$this->logfile);
error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" .
array2string($contact)."\n",3,$this->logfile);
}
return $contact;
}