diff -urN ../z-push-2.5.orig/src/backend/searchcarddav/config.php.dist ./src/backend/searchcarddav/config.php.dist --- ../z-push-2.5.orig/src/backend/searchcarddav/config.php.dist 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/searchcarddav/config.php.dist 2022-01-29 22:32:27.000435000 +0200 @@ -0,0 +1,64 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +global $searchcarddav_conf; +$searchcarddav_conf = array(); +$i = 0; + + +// CardDAV URL +$searchcarddav_conf[$i]["CARDDAV_URL"] = "https://dav.domain.tld/radicale/%u/"; + +$i++; +$searchcarddav_conf[$i]["CARDDAV_URL"] = "https://dav2.domain.tld/radicale/%u/"; + +$i++; +$searchcarddav_conf[$i]["CARDDAV_URL"] = "https://dav3.domain.tld/radicale/%u/"; + + + +// SQL field mapping. +// values correspond to the search query above +global $carddav_field_map; +$carddav_field_map = array( + SYNC_GAL_DISPLAYNAME => 'fn', + SYNC_GAL_PHONE => 'phone', + SYNC_GAL_OFFICE => 'x-department', + SYNC_GAL_TITLE => 'title', + SYNC_GAL_COMPANY => 'org', + SYNC_GAL_ALIAS => 'nickname', +// SYNC_GAL_FIRSTNAME => 'fn', +// SYNC_GAL_LASTNAME => 'fn', +// SYNC_GAL_HOMEPHONE => 'tel_home', +// SYNC_GAL_MOBILEPHONE => 'tel_cell', + SYNC_GAL_EMAILADDRESS => 'email', + ); + +// If your carddav server needs to use file extension to recover a vcard. +// Davical needs it +// SOGo official demo online needs it, but some SOGo installation don't need it, so test it +define('CARDDAV_URL_VCARD_EXTENSION', '.vcf'); diff -urN ../z-push-2.5.orig/src/backend/searchcarddav/searchcarddav.php ./src/backend/searchcarddav/searchcarddav.php --- ../z-push-2.5.orig/src/backend/searchcarddav/searchcarddav.php 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/searchcarddav/searchcarddav.php 2022-01-29 22:38:33.571815000 +0200 @@ -0,0 +1,289 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +require_once("backend/searchcarddav/config.php"); + +class BackendSearchCardDAV implements ISearchProvider{ + private $servers = null; + private $username = ''; + private $password = ''; + + // We can have multiple addressbooks, but the mobile device will only see one (all of them merged) + private $addressbooks = array(); + + /** + * Constructor + * initializes the searchprovider to perform the search + * + * @access public + * @return + * @throws StatusException, FatalException + */ + public function __construct() { + global $searchcarddav_conf; + + if (!function_exists("curl_init")) { + throw new StatusException("BackendSearchCardDAV(): php-curl is not installed. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + if (empty($searchcarddav_conf) or !is_array($searchcarddav_conf) or (count($searchcarddav_conf) == 0)) { + throw new StatusException("BackendSearchCardDAV(): settings not defined. Search aborted. " . print_r($searchcarddav_conf), SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + $this->username = $_SERVER['PHP_AUTH_USER']; + if (empty($this->username)) $this->username = $_GET['User']; + if (empty($this->username)) { + throw new StatusException("BackendSearchCardDAV(): Unable to determine mail user. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + $this->username = $_SERVER['PHP_AUTH_USER']; + $this->password = $_SERVER['PHP_AUTH_PW']; + + for ($i = 0; $i < count($searchcarddav_conf) ; $i++) { + if (empty($searchcarddav_conf[$i]["CARDDAV_URL"])) { + throw new StatusException("BackendSearchCardDAV(): CARDDAV_URL not defined. Search aborted. " . print_r($searchcarddav_conf), SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + $carddav_url = str_replace("%u", $this->username, $searchcarddav_conf[$i]["CARDDAV_URL"]); + + $server = new carddav_backend($carddav_url, CARDDAV_URL_VCARD_EXTENSION); + $server->set_auth($this->username, $this->password); + + if (($connected = $server->check_connection())) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchCardDAV(): User '%s' is authenticated on '%s'", $this->username, $carddav_url)); + $this->servers[$i] = $server; + $this->addressbooks[$i] = array(); + + // Autodiscover all the addressbooks + $raw = $server->get(false, false, true); + if ($raw !== false) { + $xml = new SimpleXMLElement($raw); + if ($xml->addressbook_element) { + $elements = $xml->addressbook_element; + } elseif ($xml->element) { + $elements = $xml->element; + } else { + $elements = array(); + } + unset($xml); + foreach ($elements as $response) { + if (empty($response->url) and !empty($response->id)) { + $response->url = $carddav_url . $response->id . '/'; + } +// if (($searchcarddav_conf[$i]["CARDDAV_GAL_URL"]) and (strcmp(urldecode($response->url), $searchcarddav_conf[$i]["CARDDAV_GAL_URL"]) == 0)) { +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchCardDAV(): Ignoring GAL addressbook '%s'", $this->gal_url)); +// } else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchCardDAV(): Found addressbook '%s'", urldecode($response->url))); + $this->addressbooks[$i][] = urldecode($response->url); +// } + } + if (empty($this->addressbooks[$i])) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendSearchCardDAV(): Addressbooks for user %s not found on %s. Search aborted.", $this->username, $this->servers[$i])); + } + unset($response); + unset($elements); + } + } else { + //TODO: get error message + $error = ''; + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendSearchCardDAV(): User '%s' failed to authenticate on '%s': %s", $this->username, $carddav_url, $error)); + $this->servers[$i] = null; + } + } + } + + /** + * Indicates if a search type is supported by this SearchProvider + * Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented + * + * @param string $searchtype + * + * @access public + * @return boolean + */ + public function SupportsType($searchtype) { + return ($searchtype == ISearchProvider::SEARCH_GAL); + } + + /** + * Searches the GAL + * + * @param string $searchquery string to be searched for + * @param string $searchrange specified searchrange + * @param SyncResolveRecipientsPicture $searchpicture limitations for picture + * + * @access public + * @return array search results + * @throws StatusException + */ + public function GetGALSearchResults($searchquery, $searchrange, $searchpicture) { + global $carddav_field_map; + global $searchcarddav_conf; + + $items = array(); + $unique = array(); + + for ($i = 0; $i < count($this->servers) ; $i++) { + if (($this->servers[$i] !== null) and (!empty($this->addressbooks[$i]))) { + foreach ($this->addressbooks[$i] as $addressbook) { + $server = $this->servers[$i]; + try { + $server->set_url($addressbook); + $raw = $server->get(); +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendSearchCardDAV(): XML response: %s", $raw)); + + if ($raw !== false) { + $xml = new SimpleXMLElement($raw); + + $vcards = array(); + foreach ($xml->element as $element) { + if (!empty($element->vcard)) { + $vcard_lines = preg_split('/\r?\n/', $element->vcard); + $vcard = array(); + foreach ($vcard_lines as $vcard_line) { + if (!empty($vcard_line)) { + $pos = strpos($vcard_line, ':'); + if ($pos !== false) { + $vcard_key = preg_replace('/;type=.+/', '', strtolower(substr($vcard_line, 0, $pos))); + $vcard_value = substr($vcard_line, ($pos + 1)); + if (($vcard_key == "begin") and (strtolower($vcard_value) == "vcard") and (count($vcard) > 0) and (!empty($vcard['vcard_email']))) { + $key = ($vcard['uid'] ? $vcard['uid'] : $vcard['email']); + if (empty($vcard['rev'])) $vcard['rev'] = ''; + if (empty($vcards[$key]) or empty($vcards[$key]['rev']) or ($vcards[$key]['rev'] < $vcard['rev'])) { + $vcards[$key] = $vcard; + } + $vcard = array(); + } + $vcard[$vcard_key] = $vcard_value; + } + } + } + if ((count($vcard) > 0) and (!empty($vcard['email']))) { + $key = ($vcard['uid'] ? $vcard['uid'] : $vcard['email']); + if (empty($vcard['rev'])) $vcard['rev'] = ''; + if (empty($vcards[$key]) or empty($vcards[$key]['rev']) or ($vcards[$key]['rev'] < $vcard['rev'])) { + $vcards[$key] = $vcard; + } + } + } + } + unset($element); + unset($xml); + + $rc = count($items); + foreach ($vcards as $vcard) { + if ((strpos($vcard['email'], $searchquery) !== false) or (strpos($vcard['fn'], $searchquery) !== false)) { + if (empty($unique[$vcard['email'] . '|' . $vcard['fn']])) { + $unique[$vcard['email'] . '|' . $vcard['fn']] = true; + + foreach ($carddav_field_map as $key => $value ) { + if (isset($vcard[$value])) { + $items[$rc][$key] = $vcard[$value]; + } + } + + $rc++; + } + } + } + } + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendSearchCardDAV: Error getting data from '%s': %s", $addressbook, $ex->getMessage())); + } + } + } + } + +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("got total items: %s", print_r($items, true))); + + $searchtotal = count($items); + + $rangestart = 0; + $rangeend = 50; + if (!empty($searchrange) and ($searchrange != '0')) { + $pos = strpos($searchrange, '-'); + if ($pos !== false) { + $rangestart = substr($searchrange, 0, $pos); + $rangeend = substr($searchrange, ($pos + 1)); + } + } + + while (count($items) > $rangeend + 1) array_pop($items); + for ($i = 0; $i < $rangestart; $i++) { + if (count($items) > 0) array_shift($items); + } + $items['range'] = $rangestart . '-' . ($rangestart + count($items) - 1); + $items['searchtotal'] = $searchtotal; + +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("got ranged items: %s", print_r($items, true))); + + return $items; + } + + /** + * Searches for the emails on the server + * + * @param ContentParameter $cpo + * + * @return array + */ + public function GetMailboxSearchResults($cpo){ + return array(); + } + + /** + * Terminates a search for a given PID + * + * @param int $pid + * + * @return boolean + */ + public function TerminateSearch($pid) { + return true; + } + + /** + * Disconnects from the current search provider + * + * @access public + * @return boolean + */ + public function Disconnect() { + if ($this->servers != null) { + foreach ($this->servers as $server) { + if ($server->disconnect()) { + unset($server); + } + } + $this->servers = null; + } + + ZLog::Write(LOGLEVEL_DEBUG, "BackendSearchCardDAV->Disconnect(): disconnected from CardDAV servers"); + + return true; + } +}