diff -urN ../z-push-2.6.4+0.orig/src/backend/contactsldap/config.php.dist ./src/backend/contactsldap/config.php.dist --- ../z-push-2.6.4+0.orig/src/backend/contactsldap/config.php.dist 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/contactsldap/config.php.dist 2023-06-17 20:10:33.233465000 +0300 @@ -0,0 +1,110 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +global $contactsldap_conf; +$contactsldap_conf = []; + + +// IMAP settings to authenticate users +$contactsldap_conf['CONTACTSLDAP_IMAP_SERVER'] = '{localhost:993/imap/ssl/novalidate-cert}'; +//$contactsldap_conf['CONTACTSLDAP_IMAP_SERVER_TIMEOUT'] = 30; + + +// LDAP server uri +$contactsldap_conf['LDAP_SERVER_URI'] = 'ldap://127.0.0.1:389/'; +$contactsldap_conf['LDAP_START_TLS'] = false; + +//$contactsldap_conf['LDAP_SERVER_URI'] = 'ldap://127.0.0.1:636/'; +//$contactsldap_conf['LDAP_DISABLE_REFERRALS'] = true; + + +// Multi servers settings +//$contactsldap_conf['LDAP_SERVER_CONF'] = array( +// "ldap://127.0.0.1:389/", +// array("LDAP_SERVER_URI" => "ldap://192.168.1.1:3268/", "LDAP_START_TLS" => true), +// array("LDAP_SERVER_URI" => "ldaps://127.0.0.1:636/", "LDAP_START_TLS" => false, "LDAP_DISABLE_REFERRALS" => true) +//)); +//$contactsldap_conf['LDAP_BIND_TIMEOUT'] = 10; + + +// Set USER and PASSWORD if not using anonymous bind +$contactsldap_conf['ANONYMOUS_BIND'] = true; +$contactsldap_conf['LDAP_BIND_USER'] = 'cn=searchuser,dc=test,dc=net'; +$contactsldap_conf['LDAP_BIND_PASSWORD'] = ''; + +// Search base & filter +// the SEARCHVALUE string is substituded by the value inserted into the search field +$contactsldap_conf['LDAP_SEARCH_BASE'] = 'ou=global,dc=test,dc=net'; +$contactsldap_conf['LDAP_SEARCH_FILTER'] = '(&(objectclass=user)(mail=*@*))'; + +// Search attributes +$contactsldap_conf['LDAP_SEARCH_ATTR_LIST'] = []; +$contactsldap_conf['LDAP_SEARCH_ID_ATTR_LIST'] = ['objectGUID', 'objectSid', 'userPrincipalName', 'sAMAccountName', 'mail']; +$contactsldap_conf['LDAP_SEARCH_ETAG_ATTR_LIST'] = [ + ['objectGUID', 'objectSid', 'userPrincipalName', 'sAMAccountName', 'mail'], + '-', + ['uSNChanged', 'whenChanged'], + ]; +$contactsldap_conf['LDAP_SEARCH_LAST_MODIFIED_ATTR_LIST'] = ['whenChanged', 'uSNChanged']; + +// LDAP field mapping +// values correspond to an inetOrgPerson class +$contactsldap_conf['LDAP_FIELD_MAP'] = [ + 'email1address' => 'mail', +// 'email1address' => 'mail', +// 'email1address' => 'mail', + +// 'fileas' => 'displayName', + 'fileas' => 'cn', +// 'fileas' => 'name', + 'lastname' => 'sn', + 'firstname' => 'givenname', +// 'middlename' => '?', +// 'suffix' => '?', +// 'nickname' => '?', +// 'bday' => '?', + 'webpage' => 'wwwhomepage', + 'jobtitle' => 'title', + 'companyname' => 'company', + 'department' => 'department', +// 'categories' => '?', + 'homepostalcode' => 'postalcode', + 'homecountry' => 'co', + 'homestate' => 'st', + 'homecity' => 'l', + 'homestreet' => 'streetaddress', + 'body' => 'info', + 'businessphonenumber' => 'telephonenumber', + 'homephonenumber' => 'homephone', + 'mobilephonenumber' => 'mobile', + 'pagernumber' => 'otherpager', + ]; + +// Addressbook display name, the name showed in the mobile device +// %u: replaced with the username +// %d: replaced with the domain +$contactsldap_conf['CONTACTSLDAP_FOLDER_NAME'] = '%u Addressbook'; + +// Minimal length for the search pattern to do the real search. +$contactsldap_conf['CONTACTSLDAP_GAL_MIN_LENGTH'] = 5; diff -urN ../z-push-2.6.4+0.orig/src/backend/contactsldap/contactsldap.php ./src/backend/contactsldap/contactsldap.php --- ../z-push-2.6.4+0.orig/src/backend/contactsldap/contactsldap.php 1970-01-01 03:00:00.000000000 +0300 +++ ./src/backend/contactsldap/contactsldap.php 2023-09-26 15:34:09.375089000 +0300 @@ -0,0 +1,1307 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +// config file +require_once("backend/contactsldap/config.php"); + +class BackendContactsLDAP extends BackendDiff implements ISearchProvider { + private $ldap_server_conf; + private $connection = false; + private $ldap_search_result = false; + + private $domain = ''; + private $username = ''; + + // Android only supports synchronizing 1 AddressBook per account, this is the foldername for Z-Push + private $foldername = "contacts"; + + // We can have multiple addressbooks, but the mobile device will only see one (all of them merged) + private $addressbooks; + + private $contactsetag; + private $changessinkinit; + private $sinkdata; + + /** + * Constructor + * + */ + public function __construct() { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::__construct() started"); + global $contactsldap_conf; + + if (!function_exists("ldap_connect")) { + throw new StatusException("BackendContactsLDAP(): php-ldap is not installed", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + $this->addressbooks = []; + $this->contactsetag = []; + $this->changessinkinit = false; + $this->sinkdata = []; + + if (isset($contactsldap_conf['LDAP_SERVER_CONF'])) { + $this->ldap_server_conf = $contactsldap_conf['LDAP_SERVER_CONF']; + } else { + $ldap_server_conf_item = []; + if (isset($contactsldap_conf['LDAP_SERVER_URI'])) { + $ldap_server_conf_item['LDAP_SERVER_URI'] = $contactsldap_conf['LDAP_SERVER_URI']; + } else { + $this->connection = false; + throw new StatusException("BackendContactsLDAP(): No LDAP server URI defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + if (isset($contactsldap_conf['LDAP_DISABLE_REFERRALS']) and ($contactsldap_conf['LDAP_DISABLE_REFERRALS'] === true)) { + $ldap_server_conf_item['LDAP_DISABLE_REFERRALS'] = true; + } + if (isset($contactsldap_conf['LDAP_START_TLS']) and ($contactsldap_conf['LDAP_START_TLS'] === true)) { + $ldap_server_conf_item['LDAP_START_TLS'] = true; + } + $this->ldap_server_conf = [$ldap_server_conf_item]; + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP: \$this->ldap_server_conf: %s", print_r($this->ldap_server_conf, true))); + +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::__construct() finished"); + } + + /** + * Authenticates the user - NOT EFFECTIVELY IMPLEMENTED + * Normally some kind of password check would be done here. + * Alternatively, the password could be ignored and an Apache + * authentication via mod_auth_* could be done + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + */ + public function Logon($username, $domain, $password) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::Logon() started; \$username: %s, \$domain: %s, \$password: %s", $username, $domain, $password)); + global $contactsldap_conf; + + if (!function_exists("imap_open")) { + throw new FatalException("BackendContactsLDAP(): php-imap module is not installed", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + } + + if (isset($contactsldap_conf['CONTACTSLDAP_IMAP_SERVER_TIMEOUT'])) { + imap_timeout(IMAP_OPENTIMEOUT, $contactsldap_conf['CONTACTSLDAP_IMAP_SERVER_TIMEOUT']); + imap_timeout(IMAP_CLOSETIMEOUT, $contactsldap_conf['CONTACTSLDAP_IMAP_SERVER_TIMEOUT']); + } + if (!$imap_handle = imap_open($contactsldap_conf['CONTACTSLDAP_IMAP_SERVER'], $username, $password)) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->Logon(): can't connect as user '%s' on '%s': %s", $username, $this->server, imap_last_error())); + return(false); + } + imap_close($imap_handle); + + while ($ldap_server_conf_item = array_shift($this->ldap_server_conf)) { + if (!is_array($ldap_server_conf_item)) { + $ldap_server_conf_item = array('LDAP_SERVER_URI' => $ldap_server_conf_item); + } + if (!isset($ldap_server_conf_item['ANONYMOUS_BIND'])) { + $ldap_server_conf_item['ANONYMOUS_BIND'] = (isset($contactsldap_conf['ANONYMOUS_BIND']) and ($contactsldap_conf['ANONYMOUS_BIND'] === true) ? true : false); + } + if (!isset($ldap_server_conf_item['LDAP_BIND_USER']) and isset($contactsldap_conf['LDAP_BIND_USER'])) { + $ldap_server_conf_item['LDAP_BIND_USER'] = $contactsldap_conf['LDAP_BIND_USER']; + } + if (!isset($ldap_server_conf_item['LDAP_BIND_PASSWORD']) and isset($contactsldap_conf['LDAP_BIND_PASSWORD'])) { + $ldap_server_conf_item['LDAP_BIND_PASSWORD'] = $contactsldap_conf['LDAP_BIND_PASSWORD']; + } + if (!isset($ldap_server_conf_item['LDAP_BIND_TIMEOUT']) and isset($contactsldap_conf['LDAP_BIND_TIMEOUT'])) { + $ldap_server_conf_item['LDAP_BIND_TIMEOUT'] = $contactsldap_conf['LDAP_BIND_TIMEOUT']; + } + +// ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP(): \$ldap_server_conf_item: %s", print_r($ldap_server_conf_item, true))); + + // Connect + if (empty($ldap_server_conf_item['LDAP_SERVER_URI'])) { + $this->connection = false; + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP(): No LDAP server URI defined. Try next server."); + continue; + } else { + throw new StatusException("BackendContactsLDAP(): No LDAP server URI defined. Search aborted.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } else { + ZLog::Write(LOGLEVEL_INFO, sprintf("BackendContactsLDAP(): Try to connect to %s", $ldap_server_conf_item['LDAP_SERVER_URI'])); + $this->connection = @ldap_connect($ldap_server_conf_item['LDAP_SERVER_URI']); + if ($this->connection === false) { + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP(): Unable to create LDAP object. May be incorrect LDAP_SERVER_URI '%s'. Try next server.", $ldap_server_conf_item['LDAP_SERVER_URI'])); + continue; + } else { + throw new StatusException(sprintf("BackendContactsLDAP(): Unable to create LDAP object. May be incorrect LDAP_SERVER_URI '%s'. Search aborted.", $ldap_server_conf_item['LDAP_SERVER_URI']), SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP(): Set LDAP_OPT_PROTOCOL_VERSION to 3"); + @ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3); + } + + if (isset($ldap_server_conf_item['LDAP_DISABLE_REFERRALS']) and ($ldap_server_conf_item['LDAP_DISABLE_REFERRALS'] === true)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP(): Set LDAP_OPT_REFERRALS to 0"); + @ldap_set_option($this->connection, LDAP_OPT_REFERRALS, 0); + } + + if (!empty($ldap_server_conf_item['LDAP_BIND_TIMEOUT'])) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP(): Set LDAP_OPT_NETWORK_TIMEOUT to %d", $ldap_server_conf_item['LDAP_BIND_TIMEOUT'])); + @ldap_set_option($this->connection, LDAP_OPT_NETWORK_TIMEOUT, $ldap_server_conf_item['LDAP_BIND_TIMEOUT']); + } + + if (isset($ldap_server_conf_item['LDAP_START_TLS']) and ($ldap_server_conf_item['LDAP_START_TLS'] === true)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP(): Try to start TLS"); + if (! @ldap_start_tls($this->connection)) { + $ldap_errno = ldap_errno($this->connection); + $ldap_error = ldap_error($this->connection); + $this->connection = false; + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP(): Could not start TLS session with server (error %d: %s). Try next server.", $ldap_errno, $ldap_error)); + continue; + } else { + throw new StatusException(sprintf("BackendContactsLDAP(): Could not start TLS session with server (error %d: %s). Search aborted.", $ldap_errno, $ldap_error), SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + } + + // Authenticate + if (isset($ldap_server_conf_item['ANONYMOUS_BIND']) and ($ldap_server_conf_item['ANONYMOUS_BIND'] === true)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP(): Try to bind anonymously"); + if(! @ldap_bind($this->connection)) { + $ldap_errno = ldap_errno($this->connection); + $ldap_error = ldap_error($this->connection); + $this->connection = false; + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP(): Could not bind anonymously to server (error %d: %s). Try next server.", $ldap_errno, $ldap_error)); + continue; + } else { + throw new StatusException(sprintf("BackendContactsLDAP(): Could not bind anonymously to server (error %d: %s). Search aborted.", $ldap_errno, $ldap_error), SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP(): Successfully bind as anonymous"); + break; + } else if (!empty($ldap_server_conf_item['LDAP_BIND_USER']) and !empty($ldap_server_conf_item['LDAP_BIND_PASSWORD'])) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP(): Try to bind as %s", $ldap_server_conf_item['LDAP_BIND_USER'])); + if(! @ldap_bind($this->connection, $ldap_server_conf_item['LDAP_BIND_USER'], $ldap_server_conf_item['LDAP_BIND_PASSWORD'])) { + $ldap_errno = ldap_errno($this->connection); + $ldap_error = ldap_error($this->connection); + $this->connection = false; + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP(): Could not bind to server with user '%s' and specified password (error %d: %s). Try next server.", $ldap_server_conf_item['LDAP_BIND_USER'], $ldap_errno, $ldap_error)); + continue; + } else { + throw new StatusException(sprintf("BackendContactsLDAP(): Could not bind to server with user '%s' and specified password (error %d: %s). Search aborted.", $ldap_server_conf_item['LDAP_BIND_USER'], $ldap_errno, $ldap_error), SYNC_SEARCHSTATUS_STORE_ACCESSDENIED, null, LOGLEVEL_ERROR); + } + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP(): Succesfully authenticated as %s", $ldap_server_conf_item['LDAP_BIND_USER'])); + break; + } else { + // it would be possible to use the users login and password to authenticate on the LDAP server + // the main $backend has to keep these values so they could be used here + $this->connection = false; + if (count($this->ldap_server_conf) > 0) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP(): neither anonymous nor default bind enabled. Other options not implemented. Try next server."); + continue; + } else { + throw new StatusException("BackendContactsLDAP(): neither anonymous nor default bind enabled. Other options not implemented. Search aborted.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + } + + $this->username = $username; + $this->domain = $domain; + + // Autodiscover all the addressbooks + $this->discoverAddressbooks(); + +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::Logon() finished"); + + return(true); + } + + /** + * Logs off + * + * @access public + * @return boolean + */ + public function Logoff() { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::Logoff() started"); + if ($this->connection) { + @ldap_close($this->connection); + } + + $this->SaveStorages(); + + unset($this->contactsetag); + unset($this->sinkdata); + unset($this->addressbooks); + + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP->Logoff(): disconnected from LDAP server"); + +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::Logoff() finished"); + return(true); + } + + /** + * Sends an e-mail + * Not implemented here + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + public function SendMail($sm) { + return false; + } + + /** + * Returns the waste basket + * Not implemented here + * + * @access public + * @return string + */ + public function GetWasteBasket() { + return false; + } + + /** + * Returns the content of the named attachment as stream + * Not implemented here + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + return false; + } + + /** + * Indicates if the backend has a ChangesSink. + * A sink is an active notification mechanism which does not need polling. + * The ContactsLDAP backend simulates a sink by polling revision dates from the contacts + * + * @access public + * @return boolean + */ + public function HasChangesSink() { + return true; + } + + /** + * The folder should be considered by the sink. + * Folders which were not initialized should not result in a notification + * of IBackend->ChangesSink(). + * + * @param string $folderid + * + * @access public + * @return boolean false if found can not be found + */ + public function ChangesSinkInitialize($folderid) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::ChangesSinkInitialize() started; \$folderid: %s", print_r($folderid, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->ChangesSinkInitialize(): \$folderid '%s'", $folderid)); + + // We don't need the actual cards, we only need to get the changes since this moment + $init_ok = true; + foreach ($this->addressbooks as $addressbook) { + try { + $this->sinkdata[$addressbook] = $this->GetContacts($addressbook, false, true); + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->ChangesSinkInitialize(): Error doing the initial sync for '%s': %s", $addressbook, $ex->getMessage())); + $init_ok = false; + } + + if ($this->sinkdata[$addressbook] === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->ChangesSinkInitialize(): Error initializing the sink for '%s'", $addressbook)); + $init_ok = false; + } + } + + $this->changessinkinit = $init_ok; + +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::ChangesSinkInitialize() finished"); + return $this->changessinkinit; + } + + /** + * The actual ChangesSink. + * For max. the $timeout value this method should block and if no changes + * are available return an empty array. + * If changes are available a list of folderids is expected. + * + * @param int $timeout max. amount of seconds to block + * + * @access public + * @return array + */ + public function ChangesSink($timeout = 30) { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::ChangesSink() started"); + $notifications = []; + $stopat = time() + $timeout - 1; + $changed = false; + + //We can get here and the ChangesSink not be initialized yet + if (!$this->changessinkinit) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP->ChangesSink(): Not initialized ChangesSink, sleep and exit"); + // We sleep and do nothing else + sleep($timeout); + return $notifications; + } + + // only check once to reduce pressure in the LDAP server + foreach ($this->addressbooks as $addressbook) { + $contacts = false; + try { + $this->ldap_search_result = false; + $contacts = $this->GetContacts($addressbook, false, true); + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->ChangesSink(): Error resyncing contacts for '%s': %s", $addressbook, $ex->getMessage())); + } + + if ($contacts === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->ChangesSink(): Error getting the changes for '%s'", $addressbook)); + } else { + if (count($contacts) != count($this->sinkdata[$addressbook])) { + // If the number of cards is different, we know for sure, there are changes + $changed = true; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->ChangesSink(): Changes detected for '%s'", $addressbook)); + } else { + // If it's the same we need to check contacts by etags + while (($contact = array_shift($contacts)) and (!$changed)) { + if ($contact['etag'] != $this->sinkdata[$addressbook][$contact['id']]['etag']) { + $changed = true; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->ChangesSink(): Changes detected for '%s'", $addressbook)); + } + } + } + unset($contacts); + + if ($changed) { + $notifications[] = $this->foldername; + } + } + } + + // Wait to timeout + if (empty($notifications)) { + while ($stopat > time()) { + sleep(1); + } + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::ChangesSink() finished; \$notifications: %s", print_r($notifications, true))); + return $notifications; + } + + /**---------------------------------------------------------------------------------------------------------- + * implemented DiffBackend methods + */ + + /** + * Returns a list (array) of folders. + * In simple implementations like this one, probably just one folder is returned. + * + * @access public + * @return array + */ + public function GetFolderList() { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::GetFolderList() started"); + ZLog::Write(LOGLEVEL_DEBUG, "BackendContactsLDAP->GetFolderList()"); + + // The mobile will only see one + $addressbooks = []; + $addressbook = $this->StatFolder($this->foldername); + $addressbooks[] = $addressbook; + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetFolderList() finished; \$addressbooks: %s", print_r($addressbooks, true))); + return $addressbooks; + } + + /** + * Returns an actual SyncFolder object + * + * @param string $id id of the folder + * + * @access public + * @return object SyncFolder with information + */ + public function GetFolder($id) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetFolder() started; \$id: %s", print_r($id, true))); + global $contactsldap_conf; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetFolder('%s')", $id)); + + $addressbook = false; + + if ($id == $this->foldername) { + $addressbook = new SyncFolder(); + $addressbook->serverid = $id; + $addressbook->parentid = "0"; + $addressbook->displayname = str_replace("%d", $this->domain, str_replace("%u", $this->username, $contactsldap_conf['CONTACTSLDAP_FOLDER_NAME'])); + $addressbook->type = SYNC_FOLDER_TYPE_CONTACT; + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetFolder() finished; \$addressbook: %s", print_r($addressbook, true))); + return $addressbook; + } + + /** + * Returns folder stats. An associative array with properties is expected. + * + * @param string $id id of the folder + * + * @access public + * @return array + */ + public function StatFolder($id) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::StatFolder() started; \$id: %s", print_r($id, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->StatFolder('%s')", $id)); + + $addressbook = $this->GetFolder($id); + + $stat = []; + $stat["id"] = $id; + $stat["parent"] = $addressbook->parentid; + $stat["mod"] = $addressbook->displayname; + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::StatFolder() finished; \$stat: %s", print_r($stat, true))); + return $stat; + } + + /** + * Creates or modifies a folder + * Not implemented here + * + * @param string $folderid id of the parent folder + * @param string $oldid if empty -> new folder created, else folder is to be renamed + * @param string $displayname new folder name (to be created, or to be renamed to) + * @param int $type folder type + * + * @access public + * @return boolean status + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function ChangeFolder($folderid, $oldid, $displayname, $type) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::ChangeFolder(): \$folderid: %s, \$oldid: %s, \$displayname: %s, \$type: %s", +//print_r($folderid, true), print_r($oldid, true), print_r($displayname, true), print_r($type, true))); + return false; + } + + /** + * Deletes a folder + * Not implemented here + * + * @param string $id + * @param string $parent is normally false + * + * @access public + * @return boolean status - false if e.g. does not exist + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function DeleteFolder($id, $parentid) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::DeleteFolder(): \$id: %s, \$parentid: %s", print_r($id, true), print_r($parentid, true))); + return false; + } + + /** + * Returns a list (array) of messages + * + * @param string $folderid id of the parent folder + * @param long $cutoffdate timestamp in the past from which on messages should be returned + * + * @access public + * @return array/false array with messages or false if folder is not available + */ + public function GetMessageList($folderid, $cutoffdate) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP->GetMessageList() started; \$folderid: %s, \$cutoffdate: %s", print_r($folderid, true), print_r($cutoffdate, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetMessageList('%s', '%s')", $folderid, $cutoffdate)); + + $messages = []; + + foreach ($this->addressbooks as $addressbook) { + $addressbookId = $this->convertAddressbookUrl($addressbook); +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP->GetMessageList(): \$addressbook %s, \$addressbookId: %s", print_r($addressbook, true), print_r($addressbookId, true))); + + $contacts = false; + try { + // We don't need the actual vcards here, we only need a list of all them + $contacts = $this->GetContacts($addressbook, false); + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->GetMessageList(): Error getting the contacts in '%s': %s", $addressbook, $ex->getMessage())); + } + + if ($contacts === false) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP->GetMessageList(): Error getting the contacts"); + } else { + foreach ($contacts as $contact) { + $id = $addressbookId . "-" . $contact['id']; + $this->contactsetag[$id] = $contact['etag']; + $messages[] = $this->StatMessage($folderid, $id); + } + } + } +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP->GetMessageList() finished; \$messages: '%s'", print_r($messages, true))); + + return $messages; + } + + /** + * Returns the actual SyncXXX object type. + * + * @param string $folderid id of the parent folder + * @param string $id id of the message + * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) + * + * @access public + * @return object/false false if the message could not be retrieved + */ + public function GetMessage($folderid, $id, $contentparameters) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetMessage() started; \$folderid: %s, \$id: %s, \$contentparameters: %s", print_r($folderid, true), print_r($id, true), print_r($contentparameters, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetMessage('%s', '%s')", $folderid, $id)); + + $message = false; + $addressbookId = $this->getAddressbookIdFromVcard($id); + $vcardId = $this->getContactIdFromVcard($id); + $addressbook = $this->getAddressbookFromId($addressbookId); + + if ($addressbook !== false) { + + $contact = false; + try { + $contact = $this->GetContact($addressbook, $vcardId); + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->GetMessage(): Error getting contact '%s' in '%s': %s", $vcardId, $addressbookId, $ex->getMessage())); + } + + if ($contact !== false) { + $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation()); + $message = $this->ParseFromLDAPContact($contact, $truncsize); + } + } + + if ($message === false) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP->GetMessage(): Contact not found"); + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetMessage() finished; \$message: %s", print_r($message, true))); + return $message; + } + + + /** + * Returns message stats, analogous to the folder stats from StatFolder(). + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return array + */ + public function StatMessage($folderid, $id) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::StatMessage() started; \$folderid: %s, \$id: %s", print_r($folderid, true), print_r($id, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->StatMessage('%s', '%s')", $folderid, $id)); + + $message = []; + if (!isset($this->contactsetag[$id])) { + $addressbookId = $this->getAddressbookIdFromVcard($id); + $vcardId = $this->getContactIdFromVcard($id); + $addressbookUrl = $this->getAddressbookFromId($addressbookId); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->StatMessage(): No contactsetag found, getting vcard '%s' in '%s'", $vcardId, $addressbookId)); + if ($addressbookUrl !== false) { + + $contact = false; + try { + $contact = $this->GetContact($addressbook, $vcardId); + } catch (Exception $ex) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->StatMessage(): Error getting contact '%s' in '%s': %s", $vcardId, $addressbookId, $ex->getMessage())); + } + + if ($contact !== false) { + $vcard = new SimpleXMLElement($xml_vcard); + $this->contactsetag[$id] = $contact['etag']; + unset($vcard); + } + unset($contact); + } + } + $message["mod"] = $this->contactsetag[$id]; + $message["id"] = $id; + $message["flags"] = 1; + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::StatMessage() finished; \$message: %s", print_r($message, true))); + return $message; + } + + /** + * Called when a message has been changed on the mobile. + * Not implemented here + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param SyncXXX $message the SyncObject containing a message + * @param ContentParameters $contentParameters + * + * @access public + * @return array same return value as StatMessage() + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function ChangeMessage($folderid, $id, $message, $contentParameters) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::ChangeMessage() started; \$folderid: %s, \$id: %s, \$message: %s, \$contentParameters: %s", $folderid, $id, print_r($message, true), print_r($contentParameters, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->ChangeMessage('%s', '%s')", $folderid, $id)); + return false; + } + + /** + * Changes the 'read' flag of a message on disk + * Not implemented here + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param int $flags read flag of the message + * @param ContentParameters $contentParameters + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function SetReadFlag($folderid, $id, $flags, $contentParameters) { + return false; + } + + /** + * Called when the user has requested to delete (really delete) a message + * Not implemented here + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param ContentParameters $contentParameters + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function DeleteMessage($folderid, $id, $contentParameters) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::DeleteMessage() started; \$folderid: %s, \$id: %s, \$contentParameters: %s", $folderid, $id, print_r($contentParameters, true))); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->DeleteMessage('%s', '%s')", $folderid, $id)); + return false; + } + + /** + * Called when the user moves an item on the PDA from one folder to another + * Not implemented here + * + * @param string $folderid id of the source folder + * @param string $id id of the message + * @param string $newfolderid id of the destination folder + * @param ContentParameters $contentParameters + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions + */ + public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) { + return false; + } + + + /** + * Resolves recipients + * + * @param SyncObject $resolveRecipients + * + * @access public + * @return SyncObject $resolveRecipients + */ + public function ResolveRecipients($resolveRecipients) { + // TODO: + return false; + } + + + /** + * Indicates which AS version is supported by the backend. + * + * @access public + * @return string AS version constant + */ + public function GetSupportedASVersion() { + return ZPush::ASV_14; + } + + + /** + * Returns the BackendContactsLDAP as it implements the ISearchProvider interface + * This could be overwritten by the global configuration + * + * @access public + * @return object Implementation of ISearchProvider + */ + public function GetSearchProvider() { + return $this; + } + + + /**---------------------------------------------------------------------------------------------------------- + * public ISearchProvider methods + */ + + /** + * 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) { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::SupportsType()"); + return ($searchtype == ISearchProvider::SEARCH_GAL); + } + + + /** + * Queries the LDAP backend + * + * @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) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetGALSearchResults() started; \$searchquery: %s, \$searchrange: %s, \$searchpicture: %s", $searchquery, print_r($searchrange, true), print_r($searchpicture, true))); + global $contactsldap_conf; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetGALSearchResults(%s, %s)", $searchquery, $searchrange)); + if ($this->gal_url !== false && $this->server !== false) { + // Don't search if the length is < 5, we are typing yet + if (strlen($searchquery) < $contactsldap_conf['CONTACTSLDAP_GAL_MIN_LENGTH']) { + return false; + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetGALSearchResults(): searching: %s", $this->gal_url)); + try { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetGALSearchResults(): server is null? %d", $this->server == null)); + $this->server->set_url($this->gal_url); + $vcards = $this->server->search_vcards(str_replace("<", "", str_replace(">", "", $searchquery)), 15, true, false, false); + } + catch (Exception $e) { + $vcards = false; + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->GetGALSearchResults(): Error in search %s", $e->getMessage())); + } + if ($vcards === false) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP->GetGALSearchResults(): Error in LDAP search query. Search aborted"); + return false; + } + + $xml_vcards = new SimpleXMLElement($vcards); + unset($vcards); + + // range for the search results, default symbian range end is 50, wm 99, + // so we'll use that of nokia + $rangestart = 0; + $rangeend = 50; + + if ($searchrange != '0') { + $pos = strpos($searchrange, '-'); + $rangestart = substr($searchrange, 0, $pos); + $rangeend = substr($searchrange, ($pos + 1)); + } + $items = []; + + // TODO the limiting of the searchresults could be refactored into Utils as it's probably used more than once + $querycnt = $xml_vcards->count(); + //do not return more results as requested in range + $querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : ($querycnt == 0 ? 1 : $querycnt); + $items['range'] = $rangestart.'-'.($querylimit - 1); + $items['searchtotal'] = $querycnt; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->GetGALSearchResults(): %s entries found, returning %s to %s", $querycnt, $rangestart, $querylimit)); + + $i = 0; + $rc = 0; + foreach ($xml_vcards->element as $xml_vcard) { + if ($i >= $rangestart && $i < $querylimit) { + $contact = $this->ParseFromLDAPContact($xml_vcard->vcard->__toString()); + if ($contact === false) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("BackendContactsLDAP->GetGALSearchResults(): error converting vCard to AS contact\n%s\n", $xml_vcard->vcard->__toString())); + } + else { + $items[$rc][SYNC_GAL_EMAILADDRESS] = $contact->email1address; + if (isset($contact->fileas)) { + $items[$rc][SYNC_GAL_DISPLAYNAME] = $contact->fileas; + } + else if (isset($contact->firstname) || isset($contact->middlename) || isset($contact->lastname)) { + $items[$rc][SYNC_GAL_DISPLAYNAME] = $contact->firstname . (isset($contact->middlename) ? " " . $contact->middlename : "") . (isset($contact->lastname) ? " " . $contact->lastname : ""); + } + else { + $items[$rc][SYNC_GAL_DISPLAYNAME] = $contact->email1address; + } + if (isset($contact->firstname)) { + $items[$rc][SYNC_GAL_FIRSTNAME] = $contact->firstname; + } + else { + $items[$rc][SYNC_GAL_FIRSTNAME] = ""; + } + if (isset($contact->lastname)) { + $items[$rc][SYNC_GAL_LASTNAME] = $contact->lastname; + } + else { + $items[$rc][SYNC_GAL_LASTNAME] = ""; + } + if (isset($contact->businessphonenumber)) { + $items[$rc][SYNC_GAL_PHONE] = $contact->businessphonenumber; + } + if (isset($contact->homephonenumber)) { + $items[$rc][SYNC_GAL_HOMEPHONE] = $contact->homephonenumber; + } + if (isset($contact->mobilephonenumber)) { + $items[$rc][SYNC_GAL_MOBILEPHONE] = $contact->mobilephonenumber; + } + if (isset($contact->title)) { + $items[$rc][SYNC_GAL_TITLE] = $contact->title; + } + if (isset($contact->companyname)) { + $items[$rc][SYNC_GAL_COMPANY] = $contact->companyname; + } + if (isset($contact->department)) { + $items[$rc][SYNC_GAL_OFFICE] = $contact->department; + } + if (isset($contact->nickname)) { + $items[$rc][SYNC_GAL_ALIAS] = $contact->nickname; + } + unset($contact); + $rc++; + } + } + $i++; + } + + unset($xml_vcards); +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetGALSearchResults() finished; \$items: %s", print_r($items, true))); + return $items; + } + else { + unset($xml_vcards); +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::GetGALSearchResults() finished; false"); + return false; + } + } + + /** + * Searches for the emails on the server + * + * @param ContentParameter $cpo + * + * @return array + */ + public function GetMailboxSearchResults($cpo) { + return false; + } + + /** + * Terminates a search for a given PID + * + * @param int $pid + * + * @return boolean + */ + public function TerminateSearch($pid) { + return true; + } + + /** + * Disconnects from LDAP + * + * @access public + * @return boolean + */ + public function Disconnect() { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::Disconnect();"); + return true; + } + + + /**---------------------------------------------------------------------------------------------------------- + * private vcard-specific internals + */ + + + public function FetchLDAPContacts($addressbook) { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::FetchLDAPContacts() started"); + global $contactsldap_conf; + + if ((!$this->ldap_search_result) and (isset($this->connection)) and ($this->connection !== false)) { + $result = @ldap_search($this->connection, $addressbook, $contactsldap_conf['LDAP_SEARCH_FILTER'], $contactsldap_conf['LDAP_SEARCH_ATTR_LIST']); + if (!$result) { + ZLog::Write(LOGLEVEL_ERROR, "BackendContactsLDAP->FetchLDAPContacts(): Error in LDAP search query. Search aborted"); + return(false); + } + + // get entry data as array + $ldap_search_result = ldap_get_entries($this->connection, $result); +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::FetchLDAPContacts(): \$ldap_search_result: %s", print_r($ldap_search_result, true))); + + $this->ldap_search_result = []; + for ($i = 0; $i < $ldap_search_result["count"]; $i++) { + if (!is_array($contactsldap_conf['LDAP_SEARCH_ID_ATTR_LIST'])) { + $contactsldap_conf['LDAP_SEARCH_ID_ATTR_LIST'] = [$contactsldap_conf['LDAP_SEARCH_ID_ATTR_LIST']]; + } + $attr_list = $contactsldap_conf['LDAP_SEARCH_ID_ATTR_LIST']; + $id = ''; + while ((!$id) and ($id_attr = array_shift($attr_list))) { + if (!empty($ldap_search_result[$i][strtolower($id_attr)][0])) { + $id = $ldap_search_result[$i][strtolower($id_attr)][0]; + } + } + if ($id) { + $this->ldap_search_result[$id]['id'] = $id; + + $this->ldap_search_result[$id]['etag'] = ''; + foreach ($contactsldap_conf['LDAP_SEARCH_ETAG_ATTR_LIST'] as $etag_attr_item) { + if (is_array($etag_attr_item)) { + while ($etag_attr = array_shift($etag_attr_item)) { + if (!empty($ldap_search_result[$i][strtolower($etag_attr)][0])) { + $this->ldap_search_result[$id]['etag'] .= $ldap_search_result[$i][strtolower($etag_attr)][0]; + break; + } + } + } else { + $this->ldap_search_result[$id]['etag'] .= $etag_attr_item; + } + } +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::FetchLDAPContacts(): \$etag: %s", $this->ldap_search_result[$id]['etag'])); + if ($this->ldap_search_result[$id]['etag']) $this->ldap_search_result[$id]['etag'] = md5($this->ldap_search_result[$id]['etag']); +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::FetchLDAPContacts(): \$etag: %s", $this->ldap_search_result[$id]['etag'])); + + if (!is_array($contactsldap_conf['LDAP_SEARCH_LAST_MODIFIED_ATTR_LIST'])) { + $contactsldap_conf['LDAP_SEARCH_LAST_MODIFIED_ATTR_LIST'] = [$contactsldap_conf['LDAP_SEARCH_LAST_MODIFIED_ATTR_LIST']]; + } + $attr_list = $contactsldap_conf['LDAP_SEARCH_LAST_MODIFIED_ATTR_LIST']; + while ((!isset($this->ldap_search_result[$id]['last_modified'])) and ($last_modified_attr = array_shift($attr_list))) { + if (!empty($ldap_search_result[$i][strtolower($last_modified_attr)][0])) { + $this->ldap_search_result[$id]['last_modified'] = $ldap_search_result[$i][strtolower($last_modified_attr)][0]; + } + } + + if (isset($this->ldap_search_result[$id]['last_modified']) and preg_match('/^(20\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)\.0Z/', $this->ldap_search_result[$id]['last_modified'], $matches)) { + $tz = date_default_timezone_get(); + date_default_timezone_set('UTC'); +// $this->ldap_search_result[$id]['last_modified'] = date(DATE_RFC822, mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1])); + $this->ldap_search_result[$id]['last_modified'] = date('D, d M Y H:i:s', mktime($matches[4], $matches[5], $matches[6], $matches[2], $matches[3], $matches[1])) . ' GMT'; + date_default_timezone_set($tz); + } + + for ($j = 0; $j < $ldap_search_result[$i]['count']; $j++) { + $this->ldap_search_result[$id][$ldap_search_result[$i][$j]] = $ldap_search_result[$i][$ldap_search_result[$i][$j]][0]; + } + } + } + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::FetchLDAPContacts() finished; \$this->ldap_search_result: %s", print_r($this->ldap_search_result, true))); + return(true); + } + + public function GetContacts($addressbook, $detailed = true, $by_id = false) { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::GetContacts() started"); + global $contactsldap_conf; + + if (!$this->ldap_search_result) { + $this->FetchLDAPContacts($addressbook); + } + if ($this->ldap_search_result) { + + $items = []; + + if ($by_id) { + // use contact id as index + if ($detailed) { + $items[] = $this->ldap_search_result; + } else { + $i = 0; + foreach ($this->ldap_search_result as $ldap_search_result) { + $items[$ldap_search_result['id']]['id'] = $ldap_search_result['id']; + $items[$ldap_search_result['id']]['etag'] = $ldap_search_result['etag']; + $items[$ldap_search_result['id']]['last_modified'] = $ldap_search_result['last_modified']; + } + } + } else { + // use $i as index + $i = 0; + foreach ($this->ldap_search_result as $ldap_search_result) { + if ($detailed) { + $items[] = $ldap_search_result; + } else { + $items[$i]['id'] = $ldap_search_result['id']; + $items[$i]['etag'] = $ldap_search_result['etag']; + $items[$i]['last_modified'] = $ldap_search_result['last_modified']; + } + + $i++; + } + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetContacts() finished; \$items: %s", print_r($items, true))); + return $items; + } else { + return(false); + } + } + + public function GetContact($addressbook, $id) { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::GetContact() started"); + global $contactsldap_conf; + + if (!$this->ldap_search_result) { + $this->FetchLDAPContacts($addressbook); + } + if ($this->ldap_search_result and (isset($this->ldap_search_result[$id]))) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::GetContact() finished; \$this->ldap_search_result[$id]: %s", print_r($this->ldap_search_result[$id], true))); + return($this->ldap_search_result[$id]); + } else { + return(false); + } + } + + /** + * Converts the LDAP contact into SyncContact. + * + * @param string $data string with the vcard + * @param int $truncsize truncate size requested + * @return SyncContact + */ + private function ParseFromLDAPContact($data, $truncsize = -1) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP->ParseFromLDAPContact() started; \$data: %s", print_r($data, true))); + global $contactsldap_conf; + + ZLog::Write(LOGLEVEL_WBXML, sprintf("BackendContactsLDAP->ParseFromLDAPContact(): vCard\n%s\n", print_r($data, true))); + + // Parse the LDAP contact + $message = new SyncContact(); + + foreach ($contactsldap_conf['LDAP_FIELD_MAP'] as $key => $value) { + if (isset($data[$value])) { + if (is_array($data[$value])) { + $message->$key = $data[$value][0]; + } else { + $message->$key = $data[$value]; + } + } + } + +// if (!empty($data['photo'])) $message->picture = base64_encode($data['photo']); + + if (!empty($message->body)) { + if (Request::GetProtocolVersion() >= 12.0) { + $message->asbody = new SyncBaseBody(); + $message->asbody->type = SYNC_BODYPREFERENCE_PLAIN; + if ($truncsize > 0 && $truncsize < strlen($message->body)) { + $message->asbody->truncated = 1; + $message->body = Utils::Utf8_truncate($message->body, $truncsize); + } else { + $message->asbody->truncated = 0; + } + $message->asbody->data = StringStreamWrapper::Open($message->body); + $message->asbody->estimatedDataSize = strlen($message->body); +// unset($message->body); + $message->body = ''; + } else { + if (($truncsize > 0) && ($truncsize < strlen($message->body))) { + $message->bodytruncated = 1; + $message->body = Utils::Utf8_truncate($message->body, $truncsize); + } else { + $message->bodytruncated = 0; + } + $message->bodysize = strlen($message->body); + } + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::ParseFromLDAPContact() finished; \$message: %s", print_r($message, true))); + return $message; + } + + /** + * Discover all the addressbooks collections for a user under a root. + * + */ + private function discoverAddressbooks() { +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::discoverAddressbooks() started"); + global $contactsldap_conf; + + $this->addressbooks = array($contactsldap_conf['LDAP_SEARCH_BASE']); +//ZLog::Write(LOGLEVEL_DEBUG, "!!!!!!!!!!! BackendContactsLDAP::discoverAddressbooks() finished"); + } + + /** + * Returns de addressbookId of a vcard. + * The vcardId sent to the device is formed as [addressbookId]-[vcardId] + * + * @param string $vcardId vcard ID in device. + * @return addressbookId + */ + private function getAddressbookIdFromVcard($vcardId) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookIdFromVcard() started; \$vcardId: %s", $vcardId)); + $parts = explode("-", $vcardId); + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookIdFromVcard() finished; \$parts[0]: %s", $parts[0])); + return $parts[0]; + } + + /** + * Returns the contact id stored in the LDAP server. + * + * @param string $vcardId vcard ID in device + * @return contact id in LDAP server + */ + private function getContactIdFromVcard($vcardId) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getContactIdFromVcard() started; \$vcardId: %s", $vcardId)); + $parts = explode("-", $vcardId); + + $id = ""; + for ($i = 1; $i < count($parts); $i++) { + if ($i > 1) { + $id .= "-"; + } + $id .= $parts[$i]; + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getContactIdFromVcard() finished; \$id: %s", $id)); + return $id; + } + + /** + * Convert an addressbook url into a zpush id. + * + * @param string $addressbookUrl AddressBook URL + * @return id or false + */ + private function convertAddressbookUrl($addressbookUrl) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::convertAddressbookUrl() started; \$addressbookUrl: %s", $addressbookUrl)); + $this->InitializePermanentStorage(); + + // check if this addressbookUrl was converted before + $addressbookId = $this->getAddressbookFromUrl($addressbookUrl); + + // nothing found, so generate a new id and put it in the cache + if ($addressbookId === false) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->convertAddressbookUrl('%s') New addressbook", $addressbookUrl)); + // generate addressbookId and add it to the mapping + $addressbookId = sprintf('%04x%04x', mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )); + + // addressbookId to addressbookUrl mapping + if (!isset($this->permanentStorage->fmAidAurl)) + $this->permanentStorage->fmAidAurl = []; + + $a = $this->permanentStorage->fmAidAurl; + $a[$addressbookId] = $addressbookUrl; + $this->permanentStorage->fmAidAurl = $a; + + // addressbookUrl to addressbookId mapping + if (!isset($this->permanentStorage->fmAurlAid)) + $this->permanentStorage->fmAurlAid = []; + + $b = $this->permanentStorage->fmAurlAid; + $b[$addressbookUrl] = $addressbookId; + $this->permanentStorage->fmAurlAid = $b; + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->convertAddressbookUrl('%s') = %s", $addressbookUrl, $addressbookId)); + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::convertAddressbookUrl() finished; \$addressbookId: %s", $addressbookId)); + return $addressbookId; + } + + /** + * Get the URL of an addressbook zpush id. + * + * @param string $addressbookId AddressBook Z-Push based ID + * @return url or false + */ + private function getAddressbookFromId($addressbookId) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookFromId() started; \$addressbookId: %s", $addressbookId)); + $this->InitializePermanentStorage(); + + $addressbookUrl = false; + + if (isset($this->permanentStorage->fmAidAurl)) { + if (isset($this->permanentStorage->fmAidAurl[$addressbookId])) { + $addressbookUrl = $this->permanentStorage->fmAidAurl[$addressbookId]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->getAddressbookFromId('%s') = %s", $addressbookId, $addressbookUrl)); + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendContactsLDAP->getAddressbookFromId('%s') = %s", $addressbookId, 'not found')); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->getAddressbookFromId('%s') = %s", $addressbookId, 'not initialized!')); + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookFromId() finished; \$addressbookUrl: %s", $addressbookUrl)); + return $addressbookUrl; + } + + /** + * Get the zpush id of an addressbook. + * + * @param string $addressbookUrl AddressBook URL + * @return id or false + */ + private function getAddressbookFromUrl($addressbookUrl) { +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookFromUrl() started; \$addressbookUrl: %s", $addressbookUrl)); + $this->InitializePermanentStorage(); + + $addressbookId = false; + + if (isset($this->permanentStorage->fmAurlAid)) { + if (isset($this->permanentStorage->fmAurlAid[$addressbookUrl])) { + $addressbookId = $this->permanentStorage->fmAurlAid[$addressbookUrl]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->getAddressbookFromUrl('%s') = %s", $addressbookUrl, $addressbookId)); + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->getAddressbookFromUrl('%s') = %s", $addressbookUrl, 'not found')); + } + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendContactsLDAP->getAddressbookFromUrl('%s') = %s", $addressbookUrl, 'not initialized!')); + } + +//ZLog::Write(LOGLEVEL_DEBUG, sprintf("!!!!!!!!!!! BackendContactsLDAP::getAddressbookFromUrl() finished; \$addressbookId: %s", $addressbookId)); + return $addressbookId; + } + +}