From f499955670eaf6d0bedf93dc6126353ad911e417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lehrke?= Date: Sun, 7 Mar 2010 12:48:10 +0000 Subject: [PATCH] Apply Addressbook SyncML and GroupDAV changes --- addressbook/inc/class.addressbook_bo.inc.php | 183 ++++++++++++------ .../inc/class.addressbook_groupdav.inc.php | 166 ++++++++++++---- addressbook/inc/class.addressbook_sif.inc.php | 93 ++++++--- .../inc/class.addressbook_tracking.inc.php | 10 +- addressbook/inc/class.addressbook_ui.inc.php | 12 +- .../inc/class.addressbook_vcal.inc.php | 64 ++++-- 6 files changed, 368 insertions(+), 160 deletions(-) diff --git a/addressbook/inc/class.addressbook_bo.inc.php b/addressbook/inc/class.addressbook_bo.inc.php index edbf60917a..30114220bf 100755 --- a/addressbook/inc/class.addressbook_bo.inc.php +++ b/addressbook/inc/class.addressbook_bo.inc.php @@ -18,12 +18,6 @@ */ class addressbook_bo extends addressbook_so { - /** - * @var int $tz_offset_s offset in secconds between user and server-time, - * it need to be add to a server-time to get the user-time or substracted from a user-time to get the server-time - */ - var $tz_offset_s; - /** * @var int $now_su actual user (!) time */ @@ -95,6 +89,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 * @@ -129,9 +130,7 @@ class addressbook_bo extends addressbook_so function __construct($contact_app='addressbook') { parent::__construct($contact_app); - - $this->tz_offset_s = 3600 * $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset']; - $this->now_su = time() + $this->tz_offset_s; + $this->now_su = egw_time::to('now','ts'); $this->prefs =& $GLOBALS['egw_info']['user']['preferences']['addressbook']; // get the default addressbook from the users prefs @@ -402,7 +401,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; } @@ -574,15 +573,18 @@ class addressbook_bo extends addressbook_so * This function needs to be reimplemented in the derived class * * @param array $data + * @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format + * + * @return array updated data */ - function db2data($data) + function db2data($data, $date_format='ts') { // 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; + $data[$name] = egw_time::server2user($data[$name], $date_format); } } $data['photo'] = $this->photo_src($data['id'],$data['jpegphoto']); @@ -620,15 +622,18 @@ class addressbook_bo extends addressbook_so * this needs to be reimplemented in the derived class * * @param array $data + * @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format + * + * @return array upated data */ - function data2db($data) + function data2db($data, $date_format='ts') { // 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; + $data[$name] = egw_time::server2user($data[$name], $date_format); } } return $data; @@ -730,11 +735,11 @@ class addressbook_bo extends addressbook_so $contact['modifier'] = $this->user; $contact['modified'] = $this->now_su; // set full name and fileas from the content - if (strlen($fullname = $this->fullname($contact)) > 0 && (!isset($contact['n_fn']) || $contact['n_fn'] != $fullname)) { - $contact['n_fn'] = $fullname; + if (!isset($contact['n_fn'])) + { + $contact['n_fn'] = $this->fullname($contact); if (isset($contact['org_name'])) $contact['n_fileas'] = $this->fileas($contact); } - unset($fullname); $to_write = $contact; // (non-admin) user editing his own account, make sure he does not change fields he is not allowed to (eg. via SyncML or xmlrpc) if (!$ignore_acl && !$contact['owner'] && !$this->is_admin($contact)) @@ -773,8 +778,8 @@ class addressbook_bo extends addressbook_so /** * reads contacts matched by key and puts all cols in the data array * - * @param int/string $contact_id - * @return array/boolean array with contact data, null if not found or false on no view perms + * @param int|string $contact_id + * @return array|boolean array with contact data, null if not found or false on no view perms */ function read($contact_id) { @@ -1649,38 +1654,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'); @@ -1801,28 +1823,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 { @@ -1847,16 +1888,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; @@ -1895,15 +1945,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; } - } diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 39f93fc189..b67377c448 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -51,10 +51,11 @@ class addressbook_groupdav extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->bo = new addressbook_bo(); } @@ -67,7 +68,7 @@ class addressbook_groupdav extends groupdav_handler */ static function get_path($contact) { - return '/addressbook/'.$contact[self::PATH_ATTRIBUTE].'.vcf'; + return $contact[self::PATH_ATTRIBUTE].'.vcf'; } /** @@ -95,8 +96,9 @@ 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 (!($filter['address_data'] = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props'])) + // check if we have to return the full contact data or just the etag's + if (!($filter['address_data'] = $options['props'] == 'all' && + $options['root']['ns'] == groupdav::CARDDAV) && is_array($options['props'])) { foreach($options['props'] as $prop) { @@ -108,7 +110,7 @@ class addressbook_groupdav extends groupdav_handler } } // return iterator, calling ourself to return result in chunks - $files['files'] = new groupdav_propfind_iterator($this,$filter,$files['files']); + $files['files'] = new groupdav_propfind_iterator($this,$path,$filter,$files['files']); return true; } @@ -116,11 +118,12 @@ class addressbook_groupdav extends groupdav_handler /** * Callback for profind interator * + * @param string $path * @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) + function &propfind_callback($path,array $filter,$start=false) { $starttime = microtime(true); @@ -129,7 +132,6 @@ class addressbook_groupdav extends groupdav_handler $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',$start,$filter))) @@ -138,27 +140,27 @@ class addressbook_groupdav extends groupdav_handler { $props = array( HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($contact)), - HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/x-vcard'), + HTTP_WebDAV_Server::mkprop('getcontenttype', 'text/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) { - $content = $handler->getVCard($contact,$this->charset,false); + $content = $handler->getVCard($contact['id'],$this->charset,false); $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content)); - $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'address-data',$content,true); } else { $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it } $files[] = array( - 'path' => self::get_path($contact), + 'path' => $path.self::get_path($contact), 'props' => $props, ); } } - if ($this->debug) error_log(__METHOD__.'('.array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items'); + if ($this->debug) error_log(__METHOD__."($path,".array2string($filter).','.array2string($start).") took ".(microtime(true) - $starttime).' to return '.count($files).' items'); return $files; } @@ -265,22 +267,70 @@ class addressbook_groupdav extends groupdav_handler */ function put(&$options,$id,$user=null) { - $ok = $this->_common_get_put_delete('PUT',$options,$id); - if (!is_null($ok) && !is_array($ok)) - { - return $ok; - } - $handler = self::_get_handler(); - $contact = $handler->vcardtoegw($options['content']); + if ($this->debug) error_log(__METHOD__.'('.array2string($options).",$id,$user)"); - if (!is_null($ok)) + $oldContact = $this->_common_get_put_delete('PUT',$options,$id); + if (!is_null($oldContact) && !is_array($oldContact)) { - $contact['id'] = $ok['id']; - // dont allow the client to overwrite certain values - $contact['uid'] = $ok['uid']; - $contact['owner'] = $ok['owner']; - $contact['private'] = $ok['private']; + return $oldContact; } + + $handler = self::_get_handler(); + $vCard = htmlspecialchars_decode($options['content']); + + if (is_array($oldContact)) + { + $contactId = $oldContact['id']; + $retval = true; + } + else + { + // new entry? + if (($foundContacts = $handler->search($vCard))) + { + if (($contactId = array_shift($foundContacts)) && + ($oldContact = $this->bo->read($contactId))) + { + $retval = '301 Moved Permanently'; + } + else + { + // to be safe + $contactId = -1; + $retval = '201 Created'; + } + } + else + { + // new entry + $contactId = -1; + $retval = '201 Created'; + } + } + + $contact = $handler->vcardtoegw($vCard); + + if (is_array($contact['category'])) + { + $contact['category'] = implode(',',$this->bo->find_or_add_categories($contact['category'], $contactId)); + } + elseif ($contactId > 0) + { + $contact['category'] = $oldContact['category']; + } + if (is_array($oldContact)) + { + $contact['id'] = $oldContact['id']; + // dont allow the client to overwrite certain values + $contact['uid'] = $oldContact['uid']; + $contact['owner'] = $oldContact['owner']; + $contact['private'] = $oldContact['private']; + } + else + { + $contact['owner'] = $user; + } + if ($this->http_if_match) $contact['etag'] = self::etag2value($this->http_if_match); if (!($save_ok = $this->bo->save($contact))) @@ -292,24 +342,26 @@ class addressbook_groupdav extends groupdav_handler } return false; } + if (!isset($contact['etag'])) { - $contact = $this->read($contact['id']); + $contact = $this->read($save_ok); } header('ETag: '.$this->get_etag($contact)); - if (is_null($ok)) + if ($retval !== true) { - header($h='Location: '.$this->base_uri.self::get_path($contact)); - if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): 201 Created"); - return '201 Created'; + $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); + header($h='Location: '.$this->base_uri.$path.self::get_path($contact)); + if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): $retval"); + return $retval; } return true; } /** * Query ctag for addressbook - * + * * @return string */ public function getctag($path,$user) @@ -319,10 +371,40 @@ class addressbook_groupdav extends groupdav_handler 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'].'"'; + + $ctag = 'EGw-'.$result[0]['modified'].'-wGE'; + return $ctag; + } + + /** + * Add the privileges of the current user + * + * @param array $props=array() regular props by the groupdav handler + * @return array + */ + static function current_user_privilege_set(array $props=array()) + { + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::DAV,'current-user-privilege-set', + array(HTTP_WebDAV_Server::mkprop(groupdav::DAV,'privilege', + array(//HTTP_WebDAV_Server::mkprop(groupdav::DAV,'all',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read',''), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'read-free-busy',''), + //HTTP_WebDAV_Server::mkprop(groupdav::DAV,'read-current-user-privilege-set',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'bind',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'unbind',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-post-vevent',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-respond-vevent',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'schedule-deliver-vevent',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-properties',''), + HTTP_WebDAV_Server::mkprop(groupdav::DAV,'write-content',''), + )))); + return $props; } /** @@ -342,12 +424,19 @@ class addressbook_groupdav extends groupdav_handler * * * @link http://www.mail-archive.com/calendarserver-users@lists.macosforge.org/msg01156.html - * + * * @param array $props=array() regular props by the groupdav handler + * @param string $displayname + * @param string $base_uri=null base url of handler * @return array */ - static function extra_properties(array $props=array()) + static function extra_properties(array $props=array(), $displayname, $base_uri=null) { + // addressbook description + $displayname = $GLOBALS['egw']->translation->convert(lang('Addressbook of') . ' ' . + $displayname, + $GLOBALS['egw']->translation->charset(),'utf-8'); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-description',$displayname); // supported reports (required property for CardDAV) $props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array( HTTP_WebDAV_Server::mkprop('supported-report',array( @@ -357,6 +446,7 @@ class addressbook_groupdav extends groupdav_handler HTTP_WebDAV_Server::mkprop('report', HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-multiget','')))), )); + //$props = self::current_user_privilege_set($props); return $props; } @@ -390,7 +480,7 @@ class addressbook_groupdav extends groupdav_handler { return '412 Precondition Failed'; } - return $ok; + //return $ok; } /** diff --git a/addressbook/inc/class.addressbook_sif.inc.php b/addressbook/inc/class.addressbook_sif.inc.php index af74004751..bdb735ea61 100644 --- a/addressbook/inc/class.addressbook_sif.inc.php +++ b/addressbook/inc/class.addressbook_sif.inc.php @@ -99,21 +99,26 @@ class addressbook_sif extends addressbook_bo const xml_decl = ''; const SIF_decl = '1.1'; - 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,24 +134,26 @@ 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; - $finalContact[$key] = implode(",", $this->find_or_add_categories($categories, $_abID)); + $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" . 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,7 +292,7 @@ class addressbook_sif extends addressbook_bo break; case 'Categories': - if(!empty($value)) { + if (!empty($value)) { $value = implode(", ", $this->get_categories($value)); $value = $GLOBALS['egw']->translation->convert($value, $sysCharSet, 'utf-8'); } else { diff --git a/addressbook/inc/class.addressbook_tracking.inc.php b/addressbook/inc/class.addressbook_tracking.inc.php index eed3d77190..d932f690c5 100644 --- a/addressbook/inc/class.addressbook_tracking.inc.php +++ b/addressbook/inc/class.addressbook_tracking.inc.php @@ -122,11 +122,11 @@ class addressbook_tracking extends bo_tracking { return lang('New contact submitted by %1 at %2', $GLOBALS['egw']->common->grab_owner_name($data['creator']), - $this->datetime($data['created']-$this->tracker->tz_offset_s)); + $this->datetime($data['created'])); } return lang('Contact modified by %1 at %2', $GLOBALS['egw']->common->grab_owner_name($data['modifier']), - $this->datetime($data['modified']-$this->tracker->tz_offset_s)); + $this->datetime($data['modified'])); } /** @@ -149,9 +149,7 @@ class addressbook_tracking extends bo_tracking * Get the details of an entry * * @param array $data - * @param string $datetime_format of user to notify, eg. 'Y-m-d H:i' - * @param int $tz_offset_s offset in sec to be add to server-time to get the user-time of the user to notify - * @return array of details as array with values for keys 'label','value','type' + * return array of details as array with values for keys 'label','value','type' */ function get_details($data) { @@ -167,7 +165,7 @@ class addressbook_tracking extends bo_tracking case 'created': case 'modified': $details[$name] = array( 'label' => $label, - 'value' => $this->datetime($data[$name]-$this->contacts->tz_offset_s), + 'value' => $this->datetime($data[$name]), ); break; case 'bday': diff --git a/addressbook/inc/class.addressbook_ui.inc.php b/addressbook/inc/class.addressbook_ui.inc.php index ff7dd51424..c922304eba 100644 --- a/addressbook/inc/class.addressbook_ui.inc.php +++ b/addressbook/inc/class.addressbook_ui.inc.php @@ -1083,7 +1083,7 @@ class addressbook_ui extends addressbook_bo // hide region for address format 'postcode_city' if (($row['addr_format'] = $this->addr_format_by_country($row['adr_one_countryname']))=='postcode_city') unset($row['adr_one_region']); if (($row['addr_format2'] = $this->addr_format_by_country($row['adr_two_countryname']))=='postcode_city') unset($row['adr_two_region']); - + // respect category permissions if(!empty($row['cat_id'])) { @@ -1222,6 +1222,12 @@ class addressbook_ui extends addressbook_bo { $old_org_entry = $this->read($content['id']); } + if (isset($content['n_family']) && isset($content['n_given']) + && $content['n_family'] != $old_org_entry['n_family'] + && $content['n_given'] != $old_org_entry['n_given']) + { + unset($content['n_fn']); + } if ($this->save($content)) { $content['msg'] = lang('Contact saved'); @@ -1618,7 +1624,7 @@ class addressbook_ui extends addressbook_bo $content[$key.'2'] = $content[$key]; } } - + // respect category permissions if(!empty($content['cat_id'])) { @@ -2025,7 +2031,7 @@ class addressbook_ui extends addressbook_bo } common::egw_footer(); } - + /** * Cleanup all contacts of all users (called by Admin >> Addressbook >> Site configuration (Admin only) * diff --git a/addressbook/inc/class.addressbook_vcal.inc.php b/addressbook/inc/class.addressbook_vcal.inc.php index 4723c7bcf5..05a0d31b6f 100644 --- a/addressbook/inc/class.addressbook_vcal.inc.php +++ b/addressbook/inc/class.addressbook_vcal.inc.php @@ -123,17 +123,13 @@ class addressbook_vcal extends addressbook_bo */ function addVCard($_vcard, $_abID=null, $merge=false) { - if(!$contact = $this->vcardtoegw($_vcard, $_abID)) - { - return false; - } + if (!($contact = $this->vcardtoegw($_vcard))) 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) { @@ -143,6 +139,22 @@ class addressbook_vcal extends addressbook_bo } } } + else + { + if (isset($old_contact['account_id'])) + { + $contact['account_id'] = $old_contact['account_id']; + } + if (is_array($contact['category'])) + { + $contact['category'] = implode(',',$this->find_or_add_categories($contact['category'], $_abID)); + } + else + { + // restore from orignal + $contact['category'] = $old_contact['category']; + } + } } // update entry $contact['id'] = $_abID; @@ -150,6 +162,14 @@ 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']; + } + if (is_array($contact['category'])) + { + $contact['category'] = implode(',',$this->find_or_add_categories($contact['category'], -1)); + } } return $this->save($contact); } @@ -242,7 +262,7 @@ class addressbook_vcal extends addressbook_bo $value = trim($entry[$databaseField]); } - switch($databaseField) + switch ($databaseField) { case 'private': $value = $value ? 'PRIVATE' : 'PUBLIC'; @@ -443,10 +463,15 @@ class addressbook_vcal extends addressbook_bo function search($_vcard, $contentID=null, $relax=false) { - $result = false; + $result = array(); - if (($contact = $this->vcardtoegw($_vcard, $contentID))) + if (($contact = $this->vcardtoegw($_vcard))) { + if (is_array($contact['category'])) + { + $contact['category'] = implode(',',$this->find_or_add_categories($contact['category'], + $contentID ? $contentID : -1)); + } if ($contentID) { $contact['id'] = $contentID; @@ -465,7 +490,7 @@ class addressbook_vcal extends addressbook_bo if (is_array($_supportedFields)) $this->supportedFields = $_supportedFields; } - function vcardtoegw($_vcard, $_abID=null) + function vcardtoegw($_vcard) { // the horde class does the charset conversion. DO NOT CONVERT HERE. // be as flexible as possible @@ -661,24 +686,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++; } @@ -701,7 +726,7 @@ class addressbook_vcal extends addressbook_bo foreach ($rowNames as $vcardKey => $rowName) { - switch($rowName) + switch ($rowName) { case 'VERSION': break; @@ -879,7 +904,7 @@ class addressbook_vcal extends addressbook_bo break; case 'cat_id': - $contact[$fieldName] = implode(',',$this->find_or_add_categories($vcardValues[$vcardKey]['values'], $_abID)); + $contact[$fieldName] = $vcardValues[$vcardKey]['values']; break; case 'jpegphoto': @@ -887,8 +912,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':