Commit 7b0d5004 authored by Sebastian Kummer's avatar Sebastian Kummer

Merge branch 'develop' of https://stash.z-hub.io/scm/zp/z-push into...

Merge branch 'develop' of https://stash.z-hub.io/scm/zp/z-push into bugfix/ZP-805-update-class-constructors
parents 76595fff 19824533
...@@ -550,18 +550,21 @@ class ImportChangesICS implements IImportChanges { ...@@ -550,18 +550,21 @@ class ImportChangesICS implements IImportChanges {
// Get the entryid of the message we're moving // Get the entryid of the message we're moving
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($sk)); $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($sk));
if(!$entryid) $srcmessage = false;
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve source message id", $sk, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
//open the source message if ($entryid) {
$srcmessage = mapi_msgstore_openentry($this->store, $entryid); //open the source message
if (!$srcmessage) { $srcmessage = mapi_msgstore_openentry($this->store, $entryid);
}
if(!$entryid || !$srcmessage) {
$code = SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID; $code = SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID;
// if we move to the trash and the source message is not found, we can also just tell the mobile that we successfully moved to avoid errors (ZP-624) // if we move to the trash and the source message is not found, we can also just tell the mobile that we successfully moved to avoid errors (ZP-624)
if ($newfolder == ZPush::GetBackend()->GetWasteBasket()) { if ($newfolder == ZPush::GetBackend()->GetWasteBasket()) {
$code = SYNC_MOVEITEMSSTATUS_SUCCESS; $code = SYNC_MOVEITEMSSTATUS_SUCCESS;
} }
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source message: 0x%X", $sk, $newfolder, mapi_last_hresult()), $code); $errorCase = !$entryid ? "resolve source message id" : "open source message";
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to %s: 0x%X", $sk, $newfolder, $errorCase, mapi_last_hresult()), $code);
} }
// check if the source message is in the current syncinterval // check if the source message is in the current syncinterval
......
...@@ -337,22 +337,33 @@ class BackendKopano implements IBackend, ISearchProvider { ...@@ -337,22 +337,33 @@ class BackendKopano implements IBackend, ISearchProvider {
*/ */
public function GetHierarchy() { public function GetHierarchy() {
$folders = array(); $folders = array();
$importer = false;
$mapiprovider = new MAPIProvider($this->session, $this->store); $mapiprovider = new MAPIProvider($this->session, $this->store);
$rootfolder = mapi_msgstore_openentry($this->store); $rootfolder = mapi_msgstore_openentry($this->store);
$rootfolderprops = mapi_getprops($rootfolder, array(PR_SOURCE_KEY)); $rootfolderprops = mapi_getprops($rootfolder, array(PR_SOURCE_KEY));
$rootfoldersourcekey = bin2hex($rootfolderprops[PR_SOURCE_KEY]);
$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH); $hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
$rows = mapi_table_queryallrows($hierarchy, array(PR_ENTRYID)); $rows = mapi_table_queryallrows($hierarchy, array(PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS, PR_FOLDER_TYPE));
foreach ($rows as $row) { foreach ($rows as $row) {
$mapifolder = mapi_msgstore_openentry($this->store, $row[PR_ENTRYID]); // do not display hidden and search folders
$folder = $mapiprovider->GetFolder($mapifolder); if ((isset($row[PR_ATTR_HIDDEN]) && $row[PR_ATTR_HIDDEN]) ||
(isset($row[PR_FOLDER_TYPE]) && $row[PR_FOLDER_TYPE] == FOLDER_SEARCH) ||
if (isset($folder->parentid) && $folder->parentid != $rootfoldersourcekey) (isset($row[PR_PARENT_SOURCE_KEY]) && $row[PR_PARENT_SOURCE_KEY] == $rootfolderprops[PR_SOURCE_KEY]) ) {
continue;
}
$folder = $mapiprovider->GetFolder($row);
if ($folder) {
$folders[] = $folder; $folders[] = $folder;
}
}
// reloop the folders to make sure all parentids are mapped correctly
$dm = ZPush::GetDeviceManager();
foreach ($folders as $folder) {
if ($folder->parentid !== "0") {
$folder->parentid = $dm->GetFolderIdForBackendId($folder->parentid);
}
} }
return $folders; return $folders;
......
...@@ -155,7 +155,7 @@ class PHPWrapper { ...@@ -155,7 +155,7 @@ class PHPWrapper {
else $message->flags = $flags; else $message->flags = $flags;
$this->importer->ImportMessageChange($this->prefix.bin2hex($sourcekey), $message); $this->importer->ImportMessageChange($this->prefix.bin2hex($sourcekey), $message);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("PHPWrapper->ImportMessageChange(): change for :'%s'", $this->prefix.bin2hex($sourcekey))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("PHPWrapper->ImportMessageChange(): change for: '%s'", $this->prefix.bin2hex($sourcekey)));
// Tell MAPI it doesn't need to do anything itself, as we've done all the work already. // Tell MAPI it doesn't need to do anything itself, as we've done all the work already.
return SYNC_E_IGNORE; return SYNC_E_IGNORE;
......
...@@ -53,15 +53,20 @@ class SqlStateMachine implements IStateMachine { ...@@ -53,15 +53,20 @@ class SqlStateMachine implements IStateMachine {
const VERSION = "version"; const VERSION = "version";
const UNKNOWNDATABASE = 1049; const UNKNOWNDATABASE = 1049;
const CREATETABLE_SETTINGS = "CREATE TABLE IF NOT EXISTS settings (key_name VARCHAR(50) NOT NULL, key_value VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (key_name));"; const CREATETABLE_SETTINGS = "CREATE TABLE IF NOT EXISTS **settings** (key_name VARCHAR(50) NOT NULL, key_value VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (key_name));";
const CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS users (username VARCHAR(50) NOT NULL, device_id VARCHAR(50) NOT NULL, PRIMARY KEY (username, device_id));"; const CREATETABLE_USERS = "CREATE TABLE IF NOT EXISTS **users** (username VARCHAR(50) NOT NULL, device_id VARCHAR(50) NOT NULL, PRIMARY KEY (username, device_id));";
const CREATETABLE_STATES = "CREATE TABLE IF NOT EXISTS states (id_state INTEGER AUTO_INCREMENT, device_id VARCHAR(50) NOT NULL, uuid VARCHAR(50) NULL, state_type VARCHAR(50), counter INTEGER, state_data MEDIUMBLOB, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id_state));"; const CREATETABLE_STATES = "CREATE TABLE IF NOT EXISTS **states** (id_state INTEGER AUTO_INCREMENT, device_id VARCHAR(50) NOT NULL, uuid VARCHAR(50) NULL, state_type VARCHAR(50), counter INTEGER, state_data MEDIUMBLOB, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id_state));";
const CREATEINDEX_STATES = "CREATE UNIQUE INDEX idx_states_unique ON states (device_id, uuid, state_type, counter);"; const CREATEINDEX_STATES = "CREATE UNIQUE INDEX idx_states_unique ON **states** (device_id, uuid, state_type, counter);";
private $dbh; protected $dbh;
private $options; protected $options;
private $dsn; protected $dsn;
private $stateHashStatement; protected $stateHashStatement;
// Name of tables, which can be overwritten in extending classes
protected $settings_table = 'settings';
protected $users_table = 'users';
protected $states_table = 'states';
/** /**
* Constructor * Constructor
...@@ -184,7 +189,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -184,7 +189,7 @@ class SqlStateMachine implements IStateMachine {
if ($counter && $cleanstates) if ($counter && $cleanstates)
$this->CleanStates($devid, $type, $key, $counter); $this->CleanStates($devid, $type, $key, $counter);
$sql = "SELECT state_data FROM states WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter = :counter"; $sql = "SELECT state_data FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter = :counter";
$params = $this->getParams($devid, $type, $key, $counter); $params = $this->getParams($devid, $type, $key, $counter);
$data = null; $data = null;
...@@ -237,7 +242,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -237,7 +242,7 @@ class SqlStateMachine implements IStateMachine {
$key = $this->returnNullified($key); $key = $this->returnNullified($key);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetState(): devid:'%s' type:'%s' key:'%s' counter:'%s'", $devid, $type, Utils::PrintAsString($key), Utils::PrintAsString($counter))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->SetState(): devid:'%s' type:'%s' key:'%s' counter:'%s'", $devid, $type, Utils::PrintAsString($key), Utils::PrintAsString($counter)));
$sql = "SELECT device_id FROM states WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter = :counter"; $sql = "SELECT device_id FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter = :counter";
$params = $this->getParams($devid, $type, $key, $counter); $params = $this->getParams($devid, $type, $key, $counter);
$sth = null; $sth = null;
...@@ -251,14 +256,14 @@ class SqlStateMachine implements IStateMachine { ...@@ -251,14 +256,14 @@ class SqlStateMachine implements IStateMachine {
$record = $sth->fetch(PDO::FETCH_ASSOC); $record = $sth->fetch(PDO::FETCH_ASSOC);
if (!$record) { if (!$record) {
// New record // New record
$sql = "INSERT INTO states (device_id, state_type, uuid, counter, state_data, created_at, updated_at) VALUES (:devid, :type, :key, :counter, :data, :created_at, :updated_at)"; $sql = "INSERT INTO {$this->states_table} (device_id, state_type, uuid, counter, state_data, created_at, updated_at) VALUES (:devid, :type, :key, :counter, :data, :created_at, :updated_at)";
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
$sth->bindValue(":created_at", $this->getNow(), PDO::PARAM_STR); $sth->bindValue(":created_at", $this->getNow(), PDO::PARAM_STR);
} }
else { else {
// Existing record, we update it // Existing record, we update it
$sql = "UPDATE states SET state_data = :data, updated_at = :updated_at WHERE device_id = :devid AND state_type = :type AND uuid ". $this->getSQLOp($key) .":key AND counter = :counter"; $sql = "UPDATE {$this->states_table} SET state_data = :data, updated_at = :updated_at WHERE device_id = :devid AND state_type = :type AND uuid ". $this->getSQLOp($key) .":key AND counter = :counter";
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
} }
...@@ -309,13 +314,13 @@ class SqlStateMachine implements IStateMachine { ...@@ -309,13 +314,13 @@ class SqlStateMachine implements IStateMachine {
if ($counter === false) { if ($counter === false) {
// Remove all the states. Counter are 0 or >0, then deleting >= 0 deletes all // Remove all the states. Counter are 0 or >0, then deleting >= 0 deletes all
$sql = "DELETE FROM states WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter >= :counter"; $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter >= :counter";
} }
else if ($counter !== false && $thisCounterOnly === true) { else if ($counter !== false && $thisCounterOnly === true) {
$sql = "DELETE FROM states WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key).":key AND counter = :counter"; $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key).":key AND counter = :counter";
} }
else { else {
$sql = "DELETE FROM states WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter < :counter"; $sql = "DELETE FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid". $this->getSQLOp($key) .":key AND counter < :counter";
} }
$params = $this->getParams($devid, $type, $key, $counter); $params = $this->getParams($devid, $type, $key, $counter);
...@@ -346,7 +351,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -346,7 +351,7 @@ class SqlStateMachine implements IStateMachine {
$record = null; $record = null;
$changed = false; $changed = false;
try { try {
$sql = "SELECT username FROM users WHERE username = :username AND device_id = :devid"; $sql = "SELECT username FROM {$this->users_table} WHERE username = :username AND device_id = :devid";
$params = array(":username" => $username, ":devid" => $devid); $params = array(":username" => $username, ":devid" => $devid);
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -358,7 +363,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -358,7 +363,7 @@ class SqlStateMachine implements IStateMachine {
} }
else { else {
$sth = null; $sth = null;
$sql = "INSERT INTO users (username, device_id) VALUES (:username, :devid)"; $sql = "INSERT INTO {$this->users_table} (username, device_id) VALUES (:username, :devid)";
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
if ($sth->execute($params)) { if ($sth->execute($params)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): Linked user-device: '%s' '%s'", $username, $devid)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("SqlStateMachine->LinkUserDevice(): Linked user-device: '%s' '%s'", $username, $devid));
...@@ -391,7 +396,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -391,7 +396,7 @@ class SqlStateMachine implements IStateMachine {
$sth = null; $sth = null;
$changed = false; $changed = false;
try { try {
$sql = "DELETE FROM users WHERE username = :username AND device_id = :devid"; $sql = "DELETE FROM {$this->users_table} WHERE username = :username AND device_id = :devid";
$params = array(":username" => $username, ":devid" => $devid); $params = array(":username" => $username, ":devid" => $devid);
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -423,7 +428,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -423,7 +428,7 @@ class SqlStateMachine implements IStateMachine {
$record = null; $record = null;
$out = array(); $out = array();
try { try {
$sql = "SELECT device_id, username FROM users ORDER BY username"; $sql = "SELECT device_id, username FROM {$this->users_table} ORDER BY username";
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
$sth->execute(); $sth->execute();
...@@ -458,12 +463,12 @@ class SqlStateMachine implements IStateMachine { ...@@ -458,12 +463,12 @@ class SqlStateMachine implements IStateMachine {
$out = array(); $out = array();
try { try {
if ($username === false) { if ($username === false) {
// we also need to find potentially obsolete states that have no link to the users table anymore // we also need to find potentially obsolete states that have no link to the $this->users_table table anymore
$sql = "SELECT DISTINCT(device_id) FROM states ORDER BY device_id"; $sql = "SELECT DISTINCT(device_id) FROM {$this->states_table} ORDER BY device_id";
$params = array(); $params = array();
} }
else { else {
$sql = "SELECT device_id FROM users WHERE username = :username ORDER BY device_id"; $sql = "SELECT device_id FROM {$this->users_table} WHERE username = :username ORDER BY device_id";
$params = array(":username" => $username); $params = array(":username" => $username);
} }
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -493,7 +498,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -493,7 +498,7 @@ class SqlStateMachine implements IStateMachine {
$record = null; $record = null;
$version = IStateMachine::STATEVERSION_01; $version = IStateMachine::STATEVERSION_01;
try { try {
$sql = "SELECT key_value FROM settings WHERE key_name = :key_name"; $sql = "SELECT key_value FROM {$this->settings_table} WHERE key_name = :key_name";
$params = array(":key_name" => self::VERSION); $params = array(":key_name" => self::VERSION);
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -531,7 +536,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -531,7 +536,7 @@ class SqlStateMachine implements IStateMachine {
$record = null; $record = null;
$status = false; $status = false;
try { try {
$sql = "SELECT key_value FROM settings WHERE key_name = :key_name"; $sql = "SELECT key_value FROM {$this->settings_table} WHERE key_name = :key_name";
$params = array(":key_name" => self::VERSION); $params = array(":key_name" => self::VERSION);
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -540,7 +545,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -540,7 +545,7 @@ class SqlStateMachine implements IStateMachine {
$record = $sth->fetch(PDO::FETCH_ASSOC); $record = $sth->fetch(PDO::FETCH_ASSOC);
if ($record) { if ($record) {
$sth = null; $sth = null;
$sql = "UPDATE settings SET key_value = :value, updated_at = :updated_at WHERE key_name = :key_name"; $sql = "UPDATE {$this->settings_table} SET key_value = :value, updated_at = :updated_at WHERE key_name = :key_name";
$params[":value"] = $version; $params[":value"] = $version;
$params[":updated_at"] = $this->getNow(); $params[":updated_at"] = $this->getNow();
...@@ -551,7 +556,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -551,7 +556,7 @@ class SqlStateMachine implements IStateMachine {
} }
else { else {
$sth = null; $sth = null;
$sql = "INSERT INTO settings (key_name, key_value, created_at, updated_at) VALUES (:key_name, :value, :created_at, :updated_at)"; $sql = "INSERT INTO {$this->settings_table} (key_name, key_value, created_at, updated_at) VALUES (:key_name, :value, :created_at, :updated_at)";
$params[":value"] = $version; $params[":value"] = $version;
$params[":updated_at"] = $params[":created_at"] = $this->getNow(); $params[":updated_at"] = $params[":created_at"] = $this->getNow();
...@@ -583,7 +588,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -583,7 +588,7 @@ class SqlStateMachine implements IStateMachine {
$record = null; $record = null;
$out = array(); $out = array();
try { try {
$sql = "SELECT state_type, uuid, counter FROM states WHERE device_id = :devid ORDER BY id_state"; $sql = "SELECT state_type, uuid, counter FROM {$this->states_table} WHERE device_id = :devid ORDER BY id_state";
$params = array(":devid" => $devid); $params = array(":devid" => $devid);
$sth = $this->getDbh()->prepare($sql); $sth = $this->getDbh()->prepare($sql);
...@@ -623,9 +628,9 @@ class SqlStateMachine implements IStateMachine { ...@@ -623,9 +628,9 @@ class SqlStateMachine implements IStateMachine {
* Return a string with the datetime NOW. * Return a string with the datetime NOW.
* *
* @return string * @return string
* @access private * @access protected
*/ */
private function getNow() { protected function getNow() {
$now = new DateTime("NOW"); $now = new DateTime("NOW");
return $now->format("Y-m-d H:i:s"); return $now->format("Y-m-d H:i:s");
} }
...@@ -638,9 +643,9 @@ class SqlStateMachine implements IStateMachine { ...@@ -638,9 +643,9 @@ class SqlStateMachine implements IStateMachine {
* @params string $key * @params string $key
* @params string $counter * @params string $counter
* @return array * @return array
* @access private * @access protected
*/ */
private function getParams($devid, $type, $key, $counter) { protected function getParams($devid, $type, $key, $counter) {
return array(":devid" => $devid, ":type" => $type, ":key" => $key, ":counter" => ($counter === false ? 0 : $counter) ); return array(":devid" => $devid, ":type" => $type, ":key" => $key, ":counter" => ($counter === false ? 0 : $counter) );
} }
...@@ -650,10 +655,10 @@ class SqlStateMachine implements IStateMachine { ...@@ -650,10 +655,10 @@ class SqlStateMachine implements IStateMachine {
* *
* @param mixed $param * @param mixed $param
* *
* @access private * @access protected
* @return string * @return string
*/ */
private function getSQLOp($param) { protected function getSQLOp($param) {
if ($param == null) { if ($param == null) {
return " IS "; return " IS ";
} }
...@@ -666,7 +671,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -666,7 +671,7 @@ class SqlStateMachine implements IStateMachine {
* @param mixed $param * @param mixed $param
* @return NULL|mixed * @return NULL|mixed
*/ */
private function returnNullified($param) { protected function returnNullified($param) {
if ($param === false) { if ($param === false) {
return null; return null;
} }
...@@ -679,9 +684,9 @@ class SqlStateMachine implements IStateMachine { ...@@ -679,9 +684,9 @@ class SqlStateMachine implements IStateMachine {
* @params PDOConnection $dbh * @params PDOConnection $dbh
* @params PDOStatement $sth * @params PDOStatement $sth
* @params PDORecord $record * @params PDORecord $record
* @access private * @access protected
*/ */
private function clearConnection(&$dbh, &$sth = null, &$record = null) { protected function clearConnection(&$dbh, &$sth = null, &$record = null) {
if ($record != null) { if ($record != null) {
$record = null; $record = null;
} }
...@@ -698,12 +703,12 @@ class SqlStateMachine implements IStateMachine { ...@@ -698,12 +703,12 @@ class SqlStateMachine implements IStateMachine {
* *
* @param string $key state uuid * @param string $key state uuid
* @access private * @access protected
* @return PDOStatement * @return PDOStatement
*/ */
private function getStateHashStatement($key) { protected function getStateHashStatement($key) {
if (!isset($this->stateHashStatement) || $this->stateHashStatement == null) { if (!isset($this->stateHashStatement) || $this->stateHashStatement == null) {
$sql = "SELECT updated_at FROM states WHERE device_id = :devid AND state_type = :type AND uuid ". (($key == null) ? " IS " : " = ") . ":key AND counter = :counter"; $sql = "SELECT updated_at FROM {$this->states_table} WHERE device_id = :devid AND state_type = :type AND uuid ". (($key == null) ? " IS " : " = ") . ":key AND counter = :counter";
$this->stateHashStatement = $this->getDbh()->prepare($sql); $this->stateHashStatement = $this->getDbh()->prepare($sql);
} }
return $this->stateHashStatement; return $this->stateHashStatement;
...@@ -711,11 +716,11 @@ class SqlStateMachine implements IStateMachine { ...@@ -711,11 +716,11 @@ class SqlStateMachine implements IStateMachine {
/** /**
* Check if the database and necessary tables exist. * Check if the database and necessary tables exist.
* *
* @access private * @access protected
* @return boolean * @return boolean
* @throws UnavailableException * @throws UnavailableException
*/ */
private function checkDbAndTables() { protected function checkDbAndTables() {
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->checkDbAndTables(): Checking if database and tables are available."); ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->checkDbAndTables(): Checking if database and tables are available.");
try { try {
$sqlStmt = sprintf("SHOW TABLES FROM %s", STATE_SQL_DATABASE); $sqlStmt = sprintf("SHOW TABLES FROM %s", STATE_SQL_DATABASE);
...@@ -745,11 +750,11 @@ class SqlStateMachine implements IStateMachine { ...@@ -745,11 +750,11 @@ class SqlStateMachine implements IStateMachine {
/** /**
* Create the states database. * Create the states database.
* *
* @access private * @access protected
* @return boolean * @return boolean
* @throws UnavailableException * @throws UnavailableException
*/ */
private function createDB() { protected function createDB() {
ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->createDB(): database '%s' is not available, trying to create it.", STATE_SQL_DATABASE)); ZLog::Write(LOGLEVEL_INFO, sprintf("SqlStateMachine->createDB(): database '%s' is not available, trying to create it.", STATE_SQL_DATABASE));
$dsn = sprintf("%s:host=%s;port=%s", STATE_SQL_ENGINE, STATE_SQL_SERVER, STATE_SQL_PORT); $dsn = sprintf("%s:host=%s;port=%s", STATE_SQL_ENGINE, STATE_SQL_SERVER, STATE_SQL_PORT);
try { try {
...@@ -770,14 +775,19 @@ class SqlStateMachine implements IStateMachine { ...@@ -770,14 +775,19 @@ class SqlStateMachine implements IStateMachine {
/** /**
* Create the tables in the database. * Create the tables in the database.
* *
* @access private * @access protected
* @return boolean * @return boolean
* @throws UnavailableException * @throws UnavailableException
*/ */
private function createTables() { protected function createTables() {
ZLog::Write(LOGLEVEL_INFO, "SqlStateMachine->createTables(): tables are not available, trying to create them."); ZLog::Write(LOGLEVEL_INFO, "SqlStateMachine->createTables(): tables are not available, trying to create them.");
try { try {
$sqlStmt = self::CREATETABLE_SETTINGS . self::CREATETABLE_USERS . self::CREATETABLE_STATES . self::CREATEINDEX_STATES; $sqlStmt = strtr(self::CREATETABLE_SETTINGS . self::CREATETABLE_USERS . self::CREATETABLE_STATES . self::CREATEINDEX_STATES,
array(
'**users**' => $this->users_table,
'**states**' => $this->states_table,
'**settings**' => $this->settings_table,
));
$sth = $this->getDbh()->prepare($sqlStmt); $sth = $this->getDbh()->prepare($sqlStmt);
$sth->execute(); $sth->execute();
ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->createTables(): tables created succesfully."); ZLog::Write(LOGLEVEL_DEBUG, "SqlStateMachine->createTables(): tables created succesfully.");
...@@ -798,7 +808,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -798,7 +808,7 @@ class SqlStateMachine implements IStateMachine {
public function DoTablesHaveData() { public function DoTablesHaveData() {
try { try {
$dataSettings = $dataStates = $dataUsers = false; $dataSettings = $dataStates = $dataUsers = false;
$sqlStmt = "SELECT key_name FROM settings LIMIT 1;"; $sqlStmt = "SELECT key_name FROM {$this->settings_table} LIMIT 1;";
$sth = $this->getDbh()->prepare($sqlStmt); $sth = $this->getDbh()->prepare($sqlStmt);
$sth->execute(); $sth->execute();
if ($sth->rowCount() > 0) { if ($sth->rowCount() > 0) {
...@@ -809,7 +819,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -809,7 +819,7 @@ class SqlStateMachine implements IStateMachine {
print("There is no data in settings table." . PHP_EOL); print("There is no data in settings table." . PHP_EOL);
} }
$sqlStmt = "SELECT id_state FROM states LIMIT 1;"; $sqlStmt = "SELECT id_state FROM {$this->states_table} LIMIT 1;";
$sth = $this->getDbh()->prepare($sqlStmt); $sth = $this->getDbh()->prepare($sqlStmt);
$sth->execute(); $sth->execute();
if ($sth->rowCount() > 0) { if ($sth->rowCount() > 0) {
...@@ -820,7 +830,7 @@ class SqlStateMachine implements IStateMachine { ...@@ -820,7 +830,7 @@ class SqlStateMachine implements IStateMachine {
print("There is no data in states table." . PHP_EOL); print("There is no data in states table." . PHP_EOL);
} }
$sqlStmt = "SELECT username FROM users LIMIT 1;"; $sqlStmt = "SELECT username FROM {$this->users_table} LIMIT 1;";
$sth = $this->getDbh()->prepare($sqlStmt); $sth = $this->getDbh()->prepare($sqlStmt);
$sth->execute(); $sth->execute();
if ($sth->rowCount() > 0) { if ($sth->rowCount() > 0) {
......
...@@ -313,6 +313,8 @@ ...@@ -313,6 +313,8 @@
define('KOE_CAPABILITY_OOFTIMES', true); define('KOE_CAPABILITY_OOFTIMES', true);
// Notes support // Notes support
define('KOE_CAPABILITY_NOTES', true); define('KOE_CAPABILITY_NOTES', true);
// Shared folder support
define('KOE_CAPABILITY_SHAREDFOLDER', true);
// To synchronize the GAB KOE, the GAB store and folderid need to be specified. // To synchronize the GAB KOE, the GAB store and folderid need to be specified.
// Use the gab-sync script to generate this data. The name needs to // Use the gab-sync script to generate this data. The name needs to
......
...@@ -108,6 +108,10 @@ include_once(ZPUSH_CONFIG); ...@@ -108,6 +108,10 @@ include_once(ZPUSH_CONFIG);
// Do the actual request // Do the actual request
header(ZPush::GetServerHeader()); header(ZPush::GetServerHeader());
if (RequestProcessor::isUserAuthenticated()) {
header("X-Z-Push-Version: ". @constant('ZPUSH_VERSION'));
}
// announce the supported AS versions (if not already sent to device) // announce the supported AS versions (if not already sent to device)
if (ZPush::GetDeviceManager()->AnnounceASVersion()) { if (ZPush::GetDeviceManager()->AnnounceASVersion()) {
$versions = ZPush::GetSupportedProtocolVersions(true); $versions = ZPush::GetSupportedProtocolVersions(true);
...@@ -232,7 +236,7 @@ include_once(ZPUSH_CONFIG); ...@@ -232,7 +236,7 @@ include_once(ZPUSH_CONFIG);
ZLog::Write(LOGLEVEL_INFO, ZLog::Write(LOGLEVEL_INFO,
sprintf("cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='%s' from='%s' version='%s' method='%s' httpcode='%s'", sprintf("cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='%s' from='%s' version='%s' method='%s' httpcode='%s'",
Request::GetCommand(), Utils::FormatBytes(memory_get_peak_usage(false)), Utils::FormatBytes(memory_get_peak_usage(true)), Request::GetCommand(), Utils::FormatBytes(memory_get_peak_usage(false)), Utils::FormatBytes(memory_get_peak_usage(true)),
number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2, ',', '.'), number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2),
Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), @constant('ZPUSH_VERSION'), Request::GetMethod(), http_response_code() )); Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), @constant('ZPUSH_VERSION'), Request::GetMethod(), http_response_code() ));
ZLog::Write(LOGLEVEL_DEBUG, "-------- End"); ZLog::Write(LOGLEVEL_DEBUG, "-------- End");
......
...@@ -710,7 +710,7 @@ class ASDevice extends StateObject { ...@@ -710,7 +710,7 @@ class ASDevice extends StateObject {
$this->backend2folderidCache[$backendid] = $newHash; $this->backend2folderidCache[$backendid] = $newHash;
return $newHash; return $newHash;
} }
ZLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->GetFolderIdForBackendId(): no valid condition found for determining folderid for backendid '%s'. Returning as is!", Utils::PrintAsString($backendid))); ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice->GetFolderIdForBackendId(): no valid condition found for determining folderid for backendid '%s'. Returning as is!", Utils::PrintAsString($backendid)));
return $backendid; return $backendid;
} }
......
...@@ -53,6 +53,7 @@ class Streamer implements Serializable { ...@@ -53,6 +53,7 @@ class Streamer implements Serializable {
const STREAMER_TYPE = 3; const STREAMER_TYPE = 3;
const STREAMER_PROP = 4; const STREAMER_PROP = 4;
const STREAMER_RONOTIFY = 5; const STREAMER_RONOTIFY = 5;
const STREAMER_VALUEMAP = 20;
const STREAMER_TYPE_DATE = 1; const STREAMER_TYPE_DATE = 1;
const STREAMER_TYPE_HEX = 2; const STREAMER_TYPE_HEX = 2;
const STREAMER_TYPE_DATE_DASHES = 3; const STREAMER_TYPE_DATE_DASHES = 3;
......
...@@ -176,8 +176,13 @@ if (!defined('E_DEPRECATED')) define(E_DEPRECATED, 8192); ...@@ -176,8 +176,13 @@ if (!defined('E_DEPRECATED')) define(E_DEPRECATED, 8192);
// TODO review error handler // TODO review error handler
function zpush_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { function zpush_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
$bt = debug_backtrace(); if (defined('LOG_ERROR_MASK')) $errno &= LOG_ERROR_MASK;
switch ($errno) { switch ($errno) {
case 0:
// logging disabled by LOG_ERROR_MASK
break;
case E_DEPRECATED: case E_DEPRECATED:
// do not handle this message // do not handle this message
break; break;
...@@ -191,6 +196,7 @@ function zpush_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { ...@@ -191,6 +196,7 @@ function zpush_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
break; break;
default: default:
$bt = debug_backtrace();
ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps"); ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps");
for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) { for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) {
$file = $line = "unknown"; $file = $line = "unknown";
......
...@@ -97,6 +97,7 @@ class ZPush { ...@@ -97,6 +97,7 @@ class ZPush {
// Webservice commands // Webservice commands
const COMMAND_WEBSERVICE_DEVICE = -100; const COMMAND_WEBSERVICE_DEVICE = -100;
const COMMAND_WEBSERVICE_USERS = -101; const COMMAND_WEBSERVICE_USERS = -101;
const COMMAND_WEBSERVICE_INFO = -102;
// Latest supported State version // Latest supported State version
const STATE_VERSION = IStateMachine::STATEVERSION_02; const STATE_VERSION = IStateMachine::STATEVERSION_02;
...@@ -145,6 +146,7 @@ class ZPush { ...@@ -145,6 +146,7 @@ class ZPush {
self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND), self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
self::COMMAND_WEBSERVICE_INFO => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
); );
...@@ -366,6 +368,9 @@ class ZPush { ...@@ -366,6 +368,9 @@ class ZPush {
if (!defined('KOE_CAPABILITY_NOTES')) { if (!defined('KOE_CAPABILITY_NOTES')) {
define('KOE_CAPABILITY_NOTES', false); define('KOE_CAPABILITY_NOTES', false);
} }
if (!defined('KOE_CAPABILITY_SHAREDFOLDER')) {
define('KOE_CAPABILITY_SHAREDFOLDER', false);
}
if (!defined('KOE_GAB_FOLDERID')) { if (!defined('KOE_GAB_FOLDERID')) {
define('KOE_GAB_FOLDERID', ''); define('KOE_GAB_FOLDERID', '');
} }
...@@ -577,6 +582,8 @@ class ZPush { ...@@ -577,6 +582,8 @@ class ZPush {
static public function GetBackend() { static public function GetBackend() {
// if the backend is not yet loaded, load backend drivers and instantiate it // if the backend is not yet loaded, load backend drivers and instantiate it
if (!isset(ZPush::$backend)) { if (!isset(ZPush::$backend)) {
$isIbar = false;
// Initialize our backend // Initialize our backend
$ourBackend = @constant('BACKEND_PROVIDER'); $ourBackend = @constant('BACKEND_PROVIDER');
...@@ -594,12 +601,19 @@ class ZPush { ...@@ -594,12 +601,19 @@ class ZPush {
} }
elseif (!class_exists($ourBackend)) { elseif (!class_exists($ourBackend)) {
spl_autoload_register('\ZPush::IncludeBackend'); spl_autoload_register('\ZPush::IncludeBackend');
$isIbar = true;
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetBackend(): autoload register ZPush::IncludeBackend");
} }
if (class_exists($ourBackend)) if (class_exists($ourBackend))
ZPush::$backend = new $ourBackend(); ZPush::$backend = new $ourBackend();
else else
throw new FatalMisconfigurationException(sprintf("Backend provider '%s' can not be loaded. Check configuration!", $ourBackend)); throw new FatalMisconfigurationException(sprintf("Backend provider '%s' can not be loaded. Check configuration!", $ourBackend));
if ($isIbar) {
spl_autoload_unregister('\ZPush::IncludeBackend');
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetBackend(): autoload unregister ZPush::IncludeBackend");
}
} }
return ZPush::$backend; return ZPush::$backend;
} }
......
...@@ -189,7 +189,14 @@ class DiffState implements IChanges { ...@@ -189,7 +189,14 @@ class DiffState implements IChanges {
$changes[] = $change; $changes[] = $change;
} }
if ($old_item['mod'] != $item['mod']) { // @see https://jira.z-hub.io/browse/ZP-955
if (isset($old_item['mod']) && isset($item['mod'])) {
if ($old_item['mod'] != $item['mod']) {
$change["type"] = "change";
$changes[] = $change;
}
}
else if (isset($old_item['mod']) || isset($item['mod'])) {
$change["type"] = "change"; $change["type"] = "change";
$changes[] = $change; $changes[] = $change;
} }
......
...@@ -63,8 +63,9 @@ class Settings extends RequestProcessor { ...@@ -63,8 +63,9 @@ class Settings extends RequestProcessor {
if(KOE_CAPABILITY_RECEIVEFLAGS) $cap[] = "receiveflags"; if(KOE_CAPABILITY_RECEIVEFLAGS) $cap[] = "receiveflags";
if(KOE_CAPABILITY_SENDFLAGS) $cap[] = "sendflags"; if(KOE_CAPABILITY_SENDFLAGS) $cap[] = "sendflags";
if(KOE_CAPABILITY_OOFTIMES) $cap[] = "ooftime"; if(KOE_CAPABILITY_OOFTIMES) $cap[] = "ooftime";
else if(KOE_CAPABILITY_OOF) $cap[] = "oof"; // 'ooftime' superseeds 'oof'. If 'ooftime' is set, 'oof' should not be defined. elseif(KOE_CAPABILITY_OOF) $cap[] = "oof"; // 'ooftime' superseeds 'oof'. If 'ooftime' is set, 'oof' should not be defined.
if(KOE_CAPABILITY_NOTES) $cap[] = "notes"; if(KOE_CAPABILITY_NOTES) $cap[] = "notes";
if(KOE_CAPABILITY_SHAREDFOLDER) $cap[] = "sharedfolder";
self::$specialHeaders = array(); self::$specialHeaders = array();
self::$specialHeaders[] = "X-Push-Capabilities: ". implode(",",$cap); self::$specialHeaders[] = "X-Push-Capabilities: ". implode(",",$cap);
......
...@@ -123,7 +123,11 @@ class SyncAppointment extends SyncObject { ...@@ -123,7 +123,11 @@ class SyncAppointment extends SyncObject {
// 3 = Confident // 3 = Confident
SYNC_POOMCAL_SENSITIVITY => array ( self::STREAMER_VAR => "sensitivity", SYNC_POOMCAL_SENSITIVITY => array ( self::STREAMER_VAR => "sensitivity",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) ), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) ),
self::STREAMER_RONOTIFY => true), self::STREAMER_RONOTIFY => true,
self::STREAMER_VALUEMAP => array( 0 => "Normal",
1 => "Personal",
2 => "Private",
3 => "Confident")),
// Busystatus values // Busystatus values
// 0 = Free // 0 = Free
...@@ -134,11 +138,18 @@ class SyncAppointment extends SyncObject { ...@@ -134,11 +138,18 @@ class SyncAppointment extends SyncObject {
SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus", SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO, self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) ), self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) ),
self::STREAMER_RONOTIFY => true), self::STREAMER_RONOTIFY => true,
self::STREAMER_VALUEMAP => array( 0 => "Free",
1 => "Tentative",
2 => "Busy",
3 => "Out of office",
4 => "Working Elsewhere")),
SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent", SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO),
self::STREAMER_RONOTIFY => true), self::STREAMER_RONOTIFY => true,
self::STREAMER_VALUEMAP => array( 0 => "No",
1 => "Yes")),
SYNC_POOMCAL_REMINDER => array ( self::STREAMER_VAR => "reminder", SYNC_POOMCAL_REMINDER => array ( self::STREAMER_VAR => "reminder",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1),
...@@ -158,7 +169,16 @@ class SyncAppointment extends SyncObject { ...@@ -158,7 +169,16 @@ class SyncAppointment extends SyncObject {
// 15 = as 7 // 15 = as 7
SYNC_POOMCAL_MEETINGSTATUS => array ( self::STREAMER_VAR => "meetingstatus", SYNC_POOMCAL_MEETINGSTATUS => array ( self::STREAMER_VAR => "meetingstatus",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,3,5,7,9,11,13,15) ), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,3,5,7,9,11,13,15) ),
self::STREAMER_RONOTIFY => true), self::STREAMER_RONOTIFY => true,
self::STREAMER_VALUEMAP => array( 0 => "Not a meeting",
1 => "Meeting",
3 => "Meeting received",
5 => "Meeting canceled",
7 => "Meeting canceled and received",
9 => "Meeting",
11 => "Meeting received",
13 => "Meeting canceled",
15 => "Meeting canceled and received",)),
SYNC_POOMCAL_ATTENDEES => array ( self::STREAMER_VAR => "attendees", SYNC_POOMCAL_ATTENDEES => array ( self::STREAMER_VAR => "attendees",
self::STREAMER_TYPE => "SyncAttendee", self::STREAMER_TYPE => "SyncAttendee",
......
...@@ -126,7 +126,9 @@ class SyncMail extends SyncObject { ...@@ -126,7 +126,9 @@ class SyncMail extends SyncObject {
SYNC_POOMMAIL_READ => array ( self::STREAMER_VAR => "read", SYNC_POOMMAIL_READ => array ( self::STREAMER_VAR => "read",
self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) ), self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) ),
self::STREAMER_RONOTIFY => true), self::STREAMER_RONOTIFY => true,
self::STREAMER_VALUEMAP => array( 0 => "No",
1 => "Yes")),
SYNC_POOMMAIL_ATTACHMENTS => array ( self::STREAMER_VAR => "attachments", SYNC_POOMMAIL_ATTACHMENTS => array ( self::STREAMER_VAR => "attachments",
self::STREAMER_TYPE => "SyncAttachment", self::STREAMER_TYPE => "SyncAttachment",
......
...@@ -246,18 +246,20 @@ abstract class SyncObject extends Streamer { ...@@ -246,18 +246,20 @@ abstract class SyncObject extends Streamer {
$out[$keyprefix.$val] = Utils::GetFormattedTime($this->$val); $out[$keyprefix.$val] = Utils::GetFormattedTime($this->$val);
} }
else { else {
$out[$keyprefix.$val] = (isset($this->$val) && strlen($this->$val) ? $out[$keyprefix.$val] = (strlen($this->$val) ?
Utils::GetFormattedTime($this->$val):"undefined") ." - ". $odoName .": ". Utils::GetFormattedTime($this->$val):"undefined") ." - ". $odoName .": ".
(isset($odo->$val) && strlen($odo->$val) ? Utils::GetFormattedTime($odo->$val) : "undefined"); (strlen($odo->$val) ? Utils::GetFormattedTime($odo->$val) : "undefined");
} }
} }
// else just compare their values // else just compare their values and print human friendly if necessary
else { else {
if($this->$val == $odo->$val) { if($this->$val == $odo->$val) {
$out[$keyprefix.$val] = $this->$val; $out[$keyprefix.$val] = $this->GetNameFromPropertyValue($v, $this->$val);
} }
else { else {
$out[$keyprefix.$val] = (isset($this->$val) && $this->$val ? $this->$val:"undefined") ." - ". $odoName .": ". (isset($odo->$val) && $odo->$val ? $odo->$val:"undefined"); $out[$keyprefix.$val] = (strlen($this->$val) ? $this->GetNameFromPropertyValue($v, $this->$val) : "undefined") .
" - ". $odoName .": ".
(strlen($odo->$val) ? $odo->GetNameFromPropertyValue($v, $odo->$val) : "undefined");
} }
} }
} }
...@@ -272,7 +274,7 @@ abstract class SyncObject extends Streamer { ...@@ -272,7 +274,7 @@ abstract class SyncObject extends Streamer {
else { else {
if($this->$val == $odo->$val) { if($this->$val == $odo->$val) {
if (! ($this instanceof SyncRecurrence)) { if (! ($this instanceof SyncRecurrence)) {
$out[$keyprefix.$val] = $this->$val; $out[$keyprefix.$val] = ($this->GetNameFromPropertyValue($v, $this->$val));
} }
} }
else { else {
...@@ -280,7 +282,9 @@ abstract class SyncObject extends Streamer { ...@@ -280,7 +282,9 @@ abstract class SyncObject extends Streamer {
$out["Recurrence"] = "Recurrence changed"; $out["Recurrence"] = "Recurrence changed";
} }
else { else {
$out[$keyprefix.$val] = (strlen($this->$val) ? $this->$val:"undefined") ." - ". $odoName .": ". (strlen($odo->$val) ? $odo->$val:"undefined"); $out[$keyprefix.$val] = (strlen($this->$val) ? $this->GetNameFromPropertyValue($v, $this->$val) : "undefined") .
" - ". $odoName .": ".
(strlen($odo->$val) ? ($odo->GetNameFromPropertyValue($v, $odo->$val)) : "undefined");
} }
} }
} }
...@@ -291,7 +295,8 @@ abstract class SyncObject extends Streamer { ...@@ -291,7 +295,8 @@ abstract class SyncObject extends Streamer {
// Otherwise it's a ghosted property and the device didn't send it, so we don't have to care about that case. // Otherwise it's a ghosted property and the device didn't send it, so we don't have to care about that case.
if (in_array($k, $supportedFields)) { if (in_array($k, $supportedFields)) {
if ((is_scalar($this->$val) && strlen($this->$val)) || (!is_scalar($this->$val) && !empty($this->$val))) { if ((is_scalar($this->$val) && strlen($this->$val)) || (!is_scalar($this->$val) && !empty($this->$val))) {
$out[$keyprefix.$val] = (is_array($this->$val) ? implode(",", $this->$val) : $this->$val) . " - " . $odoName .": value completely removed"; $out[$keyprefix.$val] = (is_array($this->$val) ? implode(",", $this->$val) : $this->GetNameFromPropertyValue($v, $this->$val)) .
" - " . $odoName .": value completely removed";
} }
} }
// there is no data sent for SyncMail, so just output its values // there is no data sent for SyncMail, so just output its values
...@@ -300,7 +305,7 @@ abstract class SyncObject extends Streamer { ...@@ -300,7 +305,7 @@ abstract class SyncObject extends Streamer {
$out[$keyprefix.$val] = Utils::GetFormattedTime($this->$val); $out[$keyprefix.$val] = Utils::GetFormattedTime($this->$val);
} }
else { else {
$out[$keyprefix.$val] = $this->$val; $out[$keyprefix.$val] = $this->GetNameFromPropertyValue($v, $this->$val);
} }
} }
} }
...@@ -314,7 +319,7 @@ abstract class SyncObject extends Streamer { ...@@ -314,7 +319,7 @@ abstract class SyncObject extends Streamer {
$out[$keyprefix.$val] = "Not set - " . $odoName . ": an exception was added"; $out[$keyprefix.$val] = "Not set - " . $odoName . ": an exception was added";
} }
else { else {
$out[$keyprefix.$val] = "Not set - " . $odoName . ": " . $odo->$val . " (value added)"; $out[$keyprefix.$val] = "Not set - " . $odoName . ": " . $odo->GetNameFromPropertyValue($v, $odo->$val) . " (value added)";
} }
} }
else if (isset($v[self::STREAMER_ARRAY])) { else if (isset($v[self::STREAMER_ARRAY])) {
...@@ -322,7 +327,7 @@ abstract class SyncObject extends Streamer { ...@@ -322,7 +327,7 @@ abstract class SyncObject extends Streamer {
$out[$keyprefix.$val] = "Not set - ". $odoName .": ". implode(", ", $odo->$val) . " (value added)"; $out[$keyprefix.$val] = "Not set - ". $odoName .": ". implode(", ", $odo->$val) . " (value added)";
} }
else { else {
$out[$keyprefix.$val] = "Not set - " . $odoName . ": " . $odo->$val . " (value added)"; $out[$keyprefix.$val] = "Not set - " . $odoName . ": " . $odo->GetNameFromPropertyValue($v, $odo->$val) . " (value added)";
} }
} }
} }
...@@ -376,6 +381,19 @@ abstract class SyncObject extends Streamer { ...@@ -376,6 +381,19 @@ abstract class SyncObject extends Streamer {
return $this->unsetVars; return $this->unsetVars;
} }
/**
* Removes not necessary data from the object
*
* @access public
* @return boolean
*/
public function StripData() {
if (isset($this->unsetVars)) {
unset($this->unsetVars);
}
return parent::StripData();
}
/** /**
* Method checks if the object has the minimum of required parameters * Method checks if the object has the minimum of required parameters
* and fullfills semantic dependencies * and fullfills semantic dependencies
...@@ -587,4 +605,20 @@ abstract class SyncObject extends Streamer { ...@@ -587,4 +605,20 @@ abstract class SyncObject extends Streamer {
return true; return true;
} }
/**
* Returns human friendly property name from its value if a mapping is available.
*
* @param array $v
* @param mixed $val
*
* @access public
* @return mixed
*/
public function GetNameFromPropertyValue($v, $val) {
if (isset($v[self::STREAMER_VALUEMAP][$val])) {
return $v[self::STREAMER_VALUEMAP][$val];
}
return $val;
}
} }
...@@ -209,13 +209,14 @@ class Utils { ...@@ -209,13 +209,14 @@ class Utils {
ZLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined. Add it to your config.php."); ZLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined. Add it to your config.php.");
return null; return null;
} }
/** /**
* Checks if the PHP-MAPI extension is available and in a requested version * Checks if the PHP-MAPI extension is available and in a requested version.
* *
* @param string $version the version to be checked ("6.30.10-18495", parts or build number) * @param string $version the version to be checked ("6.30.10-18495", parts or build number)
* *
* @access public * @access public
* @return boolean installed version is superior to the checked strin * @return boolean installed version is superior to the checked string
*/ */
static public function CheckMapiExtVersion($version = "") { static public function CheckMapiExtVersion($version = "") {
// compare build number if requested // compare build number if requested
...@@ -236,8 +237,7 @@ class Utils { ...@@ -236,8 +237,7 @@ class Utils {
} }
/** /**
* Parses and returns an ecoded vCal-Uid from an * Parses and returns an ecoded vCal-Uid from an OL compatible GlobalObjectID.
* OL compatible GlobalObjectID
* *
* @param string $olUid an OL compatible GlobalObjectID * @param string $olUid an OL compatible GlobalObjectID
* *
...@@ -659,7 +659,8 @@ class Utils { ...@@ -659,7 +659,8 @@ class Utils {
// Webservice commands // Webservice commands
case ZPush::COMMAND_WEBSERVICE_DEVICE: return 'WebserviceDevice'; case ZPush::COMMAND_WEBSERVICE_DEVICE: return 'WebserviceDevice';
case ZPush::COMMAND_WEBSERVICE_USERS: return 'WebserviceUsers'; case ZPush::COMMAND_WEBSERVICE_USERS: return 'WebserviceUsers';
case ZPush::COMMAND_WEBSERVICE_INFO: return 'WebserviceInfo';
} }
return false; return false;
} }
...@@ -704,6 +705,7 @@ class Utils { ...@@ -704,6 +705,7 @@ class Utils {
// Webservice commands // Webservice commands
case 'WebserviceDevice': return ZPush::COMMAND_WEBSERVICE_DEVICE; case 'WebserviceDevice': return ZPush::COMMAND_WEBSERVICE_DEVICE;
case 'WebserviceUsers': return ZPush::COMMAND_WEBSERVICE_USERS; case 'WebserviceUsers': return ZPush::COMMAND_WEBSERVICE_USERS;
case 'WebserviceInfo': return ZPush::COMMAND_WEBSERVICE_INFO;
} }
return false; return false;
} }
......
...@@ -108,7 +108,13 @@ class ZPushAdmin { ...@@ -108,7 +108,13 @@ class ZPushAdmin {
// load all collections of device also loading states and loading hierarchy, but not checking permissions // load all collections of device also loading states and loading hierarchy, but not checking permissions
$sc->LoadAllCollections(true, true, false, true); $sc->LoadAllCollections(true, true, false, true);
}
catch (StateInvalidException $sive) {
ZLog::Write(LOGLEVEL_WARN, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' has invalid states. Please sync to solve this issue.", $devid, $user));
$device->SetDeviceError("Invalid states. Please force synchronization!");
}
if ($sc) {
if ($sc->GetLastSyncTime()) if ($sc->GetLastSyncTime())
$device->SetLastSyncTime($sc->GetLastSyncTime()); $device->SetLastSyncTime($sc->GetLastSyncTime());
...@@ -131,11 +137,6 @@ class ZPushAdmin { ...@@ -131,11 +137,6 @@ class ZPushAdmin {
} }
} }
} }
catch (StateInvalidException $sive) {
ZLog::Write(LOGLEVEL_WARN, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' has invalid states. Please sync to solve this issue.", $devid, $user));
$device->SetDeviceError("Invalid states. Please force synchronization!");
}
return $device; return $device;
} }
catch (StateNotFoundException $e) { catch (StateNotFoundException $e) {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* *
* Created : 29.12.2011 * Created : 29.12.2011
* *
* Copyright 2007 - 2013 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -69,9 +69,13 @@ class Webservice { ...@@ -69,9 +69,13 @@ class Webservice {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceDevice service", $commandCode)); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceDevice service", $commandCode));
$this->server->setClass("WebserviceDevice"); $this->server->setClass("WebserviceDevice");
} }
// the webservice command is handled by its class // the webservice command is handled by its class
if ($commandCode == ZPush::COMMAND_WEBSERVICE_USERS) { else if ($commandCode == ZPush::COMMAND_WEBSERVICE_INFO) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceInfo service", $commandCode));
$this->server->setClass("WebserviceInfo");
}
// the webservice command is handled by its class
else if ($commandCode == ZPush::COMMAND_WEBSERVICE_USERS) {
if (!defined("ALLOW_WEBSERVICE_USERS_ACCESS") || ALLOW_WEBSERVICE_USERS_ACCESS !== true) if (!defined("ALLOW_WEBSERVICE_USERS_ACCESS") || ALLOW_WEBSERVICE_USERS_ACCESS !== true)
throw new HTTPReturnCodeException("Access to the WebserviceUsers service is disabled in configuration. Enable setting ALLOW_WEBSERVICE_USERS_ACCESS", 403); throw new HTTPReturnCodeException("Access to the WebserviceUsers service is disabled in configuration. Enable setting ALLOW_WEBSERVICE_USERS_ACCESS", 403);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* Created : 23.12.2011 * Created : 23.12.2011
* *
* Copyright 2007 - 2015 Zarafa Deutschland GmbH * Copyright 2007 - 2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -42,7 +42,6 @@ ...@@ -42,7 +42,6 @@
* *
* Consult LICENSE file for details * Consult LICENSE file for details
************************************************/ ************************************************/
include ('lib/utils/zpushadmin.php');
class WebserviceDevice { class WebserviceDevice {
......
<?php
/***********************************************
* File : webserviceinfo.php
* Project : Z-Push
* Descr : Provides general information for an authenticated
* user.
*
* Created : 17.06.2016
*
* Copyright 2016 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class WebserviceInfo {
/**
* Returns a list of folders of the Request::GetGETUser().
* If the user has not enough permissions an empty result is returned.
*
* @access public
* @return array
*/
public function ListUserFolders() {
$user = Request::GetGETUser();
$output = array();
$hasRights = ZPush::GetBackend()->Setup($user);
ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceInfo::ListUserFolders(): permissions to open store '%s': %s", $user, Utils::PrintAsString($hasRights)));
$folders = ZPush::GetBackend()->GetHierarchy();
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d folders", count($folders)), true);
foreach ($folders as $folder) {
$folder->StripData();
unset($folder->Store, $folder->flags, $folder->content, $folder->NoBackendFolder, $folder->ReadOnly);
$output[] = $folder;
}
return $output;
}
}
...@@ -176,6 +176,7 @@ return array( ...@@ -176,6 +176,7 @@ return array(
'WBXMLException' => $baseDir . '/lib/exceptions/wbxmlexception.php', 'WBXMLException' => $baseDir . '/lib/exceptions/wbxmlexception.php',
'Webservice' => $baseDir . '/lib/webservice/webservice.php', 'Webservice' => $baseDir . '/lib/webservice/webservice.php',
'WebserviceDevice' => $baseDir . '/lib/webservice/webservicedevice.php', 'WebserviceDevice' => $baseDir . '/lib/webservice/webservicedevice.php',
'WebserviceInfo' => $baseDir . '/lib/webservice/webserviceinfo.php',
'WebserviceUsers' => $baseDir . '/lib/webservice/webserviceusers.php', 'WebserviceUsers' => $baseDir . '/lib/webservice/webserviceusers.php',
'ZLog' => $baseDir . '/lib/core/zlog.php', 'ZLog' => $baseDir . '/lib/core/zlog.php',
'ZPush' => $baseDir . '/lib/core/zpush.php', 'ZPush' => $baseDir . '/lib/core/zpush.php',
......
...@@ -183,6 +183,7 @@ class ComposerStaticInitd6749fc2fb9944bbe86b2b7d79a7852f ...@@ -183,6 +183,7 @@ class ComposerStaticInitd6749fc2fb9944bbe86b2b7d79a7852f
'WBXMLException' => __DIR__ . '/../..' . '/lib/exceptions/wbxmlexception.php', 'WBXMLException' => __DIR__ . '/../..' . '/lib/exceptions/wbxmlexception.php',
'Webservice' => __DIR__ . '/../..' . '/lib/webservice/webservice.php', 'Webservice' => __DIR__ . '/../..' . '/lib/webservice/webservice.php',
'WebserviceDevice' => __DIR__ . '/../..' . '/lib/webservice/webservicedevice.php', 'WebserviceDevice' => __DIR__ . '/../..' . '/lib/webservice/webservicedevice.php',
'WebserviceInfo' => __DIR__ . '/../..' . '/lib/webservice/webserviceinfo.php',
'WebserviceUsers' => __DIR__ . '/../..' . '/lib/webservice/webserviceusers.php', 'WebserviceUsers' => __DIR__ . '/../..' . '/lib/webservice/webserviceusers.php',
'ZLog' => __DIR__ . '/../..' . '/lib/core/zlog.php', 'ZLog' => __DIR__ . '/../..' . '/lib/core/zlog.php',
'ZPush' => __DIR__ . '/../..' . '/lib/core/zpush.php', 'ZPush' => __DIR__ . '/../..' . '/lib/core/zpush.php',
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* Created : 18.05.2015 * Created : 18.05.2015
* *
* Copyright 2015 Zarafa Deutschland GmbH * Copyright 2015-2016 Zarafa Deutschland GmbH
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3, * it under the terms of the GNU Affero General Public License, version 3,
...@@ -42,9 +42,10 @@ ...@@ -42,9 +42,10 @@
************************************************/ ************************************************/
if (count($argv) < 2) { if (count($argv) < 2) {
die("\tUsage: printwbmxl.php WBXML-INPUT-HERE\n\n"); die("\tUsage: printwbmxl.php WBXML-INPUT-HERE or PATH-TO-FILE\n\n");
} }
$wbxml64 = $argv[1]; $wbxml64 = is_file($argv[1]) ? realpath($argv[1]) : $argv[1];
chdir(__DIR__);
// include the stuff we need // include the stuff we need
include_once('../../src/lib/utils/stringstreamwrapper.php'); include_once('../../src/lib/utils/stringstreamwrapper.php');
...@@ -71,7 +72,7 @@ class ZLog { ...@@ -71,7 +72,7 @@ class ZLog {
} }
// setup // setup
$wxbml = StringStreamWrapper::Open($wbxml64); $wxbml = is_file($wbxml64) ? fopen($wbxml64, 'r+') : StringStreamWrapper::Open($wbxml64);
$base64filter = stream_filter_append($wxbml, 'convert.base64-decode'); $base64filter = stream_filter_append($wxbml, 'convert.base64-decode');
$decoder = new WBXMLDecoder($wxbml); $decoder = new WBXMLDecoder($wxbml);
if (! $decoder->IsWBXML()) { if (! $decoder->IsWBXML()) {
......
...@@ -88,6 +88,7 @@ class GabSyncCLI { ...@@ -88,6 +88,7 @@ class GabSyncCLI {
static private $syncWorker; static private $syncWorker;
static private $command; static private $command;
static private $uniqueId = false; static private $uniqueId = false;
static private $targetGab = false;
static private $errormessage; static private $errormessage;
/** /**
...@@ -101,6 +102,7 @@ class GabSyncCLI { ...@@ -101,6 +102,7 @@ class GabSyncCLI {
"\tgab-sync.php -a ACTION [options]" .PHP_EOL.PHP_EOL. "\tgab-sync.php -a ACTION [options]" .PHP_EOL.PHP_EOL.
"Parameters:" .PHP_EOL. "Parameters:" .PHP_EOL.
"\t-a simulate | sync | sync-one | clear-all | delete-all" .PHP_EOL. "\t-a simulate | sync | sync-one | clear-all | delete-all" .PHP_EOL.
"\t[-t] TARGET-GAB\t\t Target GAB to execute the action / unique-id on. Optional, if not set, executed on all or default gab." .PHP_EOL.
"\t[-u] UNIQUE-ID" .PHP_EOL.PHP_EOL. "\t[-u] UNIQUE-ID" .PHP_EOL.PHP_EOL.
"Actions:" .PHP_EOL. "Actions:" .PHP_EOL.
"\tsimulate\t\t Simulates the GAB synchronization and prints out statistics and configuration suggestions." .PHP_EOL. "\tsimulate\t\t Simulates the GAB synchronization and prints out statistics and configuration suggestions." .PHP_EOL.
...@@ -120,7 +122,7 @@ class GabSyncCLI { ...@@ -120,7 +122,7 @@ class GabSyncCLI {
static public function SetupSyncWorker() { static public function SetupSyncWorker() {
$file = "lib/" .strtolower(SYNCWORKER).".php"; $file = "lib/" .strtolower(SYNCWORKER).".php";
@include_once($file); include_once($file);
if (!class_exists(SYNCWORKER)) { if (!class_exists(SYNCWORKER)) {
self::$errormessage = "SyncWorker file loaded, but class '".SYNCWORKER."' can not be found. Check your configuration or implementation."; self::$errormessage = "SyncWorker file loaded, but class '".SYNCWORKER."' can not be found. Check your configuration or implementation.";
...@@ -157,7 +159,7 @@ class GabSyncCLI { ...@@ -157,7 +159,7 @@ class GabSyncCLI {
if (self::$errormessage) if (self::$errormessage)
return; return;
$options = getopt("u:a:"); $options = getopt("u:a:t:");
// get 'unique-id' // get 'unique-id'
if (isset($options['u']) && !empty($options['u'])) if (isset($options['u']) && !empty($options['u']))
...@@ -165,6 +167,12 @@ class GabSyncCLI { ...@@ -165,6 +167,12 @@ class GabSyncCLI {
else if (isset($options['unique-id']) && !empty($options['unique-id'])) else if (isset($options['unique-id']) && !empty($options['unique-id']))
self::$uniqueId = strtolower(trim($options['unique-id'])); self::$uniqueId = strtolower(trim($options['unique-id']));
// get 'target-gab'
if (isset($options['t']) && !empty($options['t']))
self::$targetGab = strtolower(trim($options['t']));
else if (isset($options['target-gab']) && !empty($options['target-gab']))
self::$targetGab = strtolower(trim($options['target-gab']));
// get 'action' // get 'action'
$action = false; $action = false;
if (isset($options['a']) && !empty($options['a'])) if (isset($options['a']) && !empty($options['a']))
...@@ -240,31 +248,31 @@ class GabSyncCLI { ...@@ -240,31 +248,31 @@ class GabSyncCLI {
echo PHP_EOL; echo PHP_EOL;
switch(self::$command) { switch(self::$command) {
case self::COMMAND_SIMULATE: case self::COMMAND_SIMULATE:
self::$syncWorker->Simulate(); self::$syncWorker->Simulate(self::$targetGab);
break; break;
case self::COMMAND_SYNC: case self::COMMAND_SYNC:
self::$syncWorker->Sync(); self::$syncWorker->Sync(self::$targetGab);
break; break;
case self::COMMAND_SYNC_ONE: case self::COMMAND_SYNC_ONE:
self::$syncWorker->SyncOne(self::$uniqueId); self::$syncWorker->SyncOne(self::$uniqueId, self::$targetGab);
break; break;
case self::COMMAND_CLEARALL: case self::COMMAND_CLEARALL:
echo "Are you sure you want to remove all chunks and data from the public folder. ALL GAB data will be removed from ALL KOE instances [y/N]: "; echo "Are you sure you want to remove all chunks and data from the hidden GAB folder. ALL GAB data will be removed from ALL KOE instances [y/N]: ";
$confirm = strtolower(trim(fgets(STDIN))); $confirm = strtolower(trim(fgets(STDIN)));
if ( $confirm === 'y' || $confirm === 'yes') if ( $confirm === 'y' || $confirm === 'yes')
self::$syncWorker->ClearAll(); self::$syncWorker->ClearAll(self::$targetGab);
else else
echo "Aborted!".PHP_EOL; echo "Aborted!".PHP_EOL;
break; break;
case self::COMMAND_DELETEALL: case self::COMMAND_DELETEALL:
echo "Are you sure you want to remove all chunks and data from the public folder and delete it? ALL GAB data will be removed from ALL KOE instances [y/N]: "; echo "Are you sure you want to remove all chunks and data from the hidden GAB folder and delete it? ALL GAB data will be removed from ALL KOE instances [y/N]: ";
$confirm = strtolower(trim(fgets(STDIN))); $confirm = strtolower(trim(fgets(STDIN)));
if ( $confirm === 'y' || $confirm === 'yes') if ( $confirm === 'y' || $confirm === 'yes')
self::$syncWorker->DeleteAll(); self::$syncWorker->DeleteAll(self::$targetGab);
else else
echo "Aborted!".PHP_EOL; echo "Aborted!".PHP_EOL;
break; break;
......
...@@ -53,11 +53,13 @@ define('PR_EMS_AB_THUMBNAIL_PHOTO', mapi_prop_tag(PT_BINARY, 0x8C9E)); ...@@ -53,11 +53,13 @@ define('PR_EMS_AB_THUMBNAIL_PHOTO', mapi_prop_tag(PT_BINARY, 0x8C9E));
class Kopano extends SyncWorker { class Kopano extends SyncWorker {
const NAME = "Z-Push Kopano GAB Sync"; const NAME = "Z-Push Kopano GAB Sync";
const VERSION = "1.0"; const VERSION = "1.1";
private $session; private $session;
private $store; private $store;
private $mainUser; private $mainUser;
private $targetStore;
private $folderCache; private $folderCache;
private $storeCache;
private $mapiprops; private $mapiprops;
/** /**
...@@ -65,13 +67,22 @@ class Kopano extends SyncWorker { ...@@ -65,13 +67,22 @@ class Kopano extends SyncWorker {
*/ */
public function __construct() { public function __construct() {
parent::__construct(); parent::__construct();
$this->session = mapi_logon_zarafa(USERNAME, PASSWORD, SERVER, CERTIFICATE, CERTIFICATE_PASSWORD, 0, self::VERSION, self::NAME. " ". self::VERSION); // send Z-Push version and user agent to ZCP >7.2.0
if ($this->checkMapiExtVersion('7.2.0')) {
$this->session = mapi_logon_zarafa(USERNAME, PASSWORD, SERVER, CERTIFICATE, CERTIFICATE_PASSWORD, 0, self::VERSION, self::NAME. " ". self::VERSION);
}
else {
$this->session = mapi_logon_zarafa(USERNAME, PASSWORD, SERVER, CERTIFICATE, CERTIFICATE_PASSWORD, 0);
}
if (mapi_last_hresult()) { if (mapi_last_hresult()) {
$this->Terminate(sprintf("Kopano: login failed with error code: 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano: login failed with error code: 0x%08X", mapi_last_hresult()));
} }
$this->mainUser = USERNAME; $this->mainUser = USERNAME;
$this->targetStore = HIDDEN_FOLDERSTORE;
$this->store = $this->openMessageStore(HIDDEN_FOLDERSTORE); $this->store = $this->openMessageStore(HIDDEN_FOLDERSTORE);
$this->folderCache = array(); $this->folderCache = array();
$this->storeCache = array();
$this->mapiprops = array( $this->mapiprops = array(
"chunktype" => "PT_STRING8:PSETID_Appointment:0x6822", // custom property "chunktype" => "PT_STRING8:PSETID_Appointment:0x6822", // custom property
...@@ -92,11 +103,15 @@ class Kopano extends SyncWorker { ...@@ -92,11 +103,15 @@ class Kopano extends SyncWorker {
/** /**
* Creates the hidden folder. * Creates the hidden folder.
* *
* @param string $gabId the id of the gab where the hidden folder should be created. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be created. If not set (null) the default gab is used.
*
* @access protected * @access protected
* @return string * @return string
*/ */
protected function CreateHiddenFolder() { protected function CreateHiddenFolder($gabId = null, $gabName = 'default') {
$parentfolder = $this->getRootFolder(); $store = $this->getStore($gabId, $gabName);
$parentfolder = $this->getRootFolder($store);
// mapi_folder_createfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION // mapi_folder_createfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION
$newfolder = mapi_folder_createfolder($parentfolder, HIDDEN_FOLDERNAME, ""); $newfolder = mapi_folder_createfolder($parentfolder, HIDDEN_FOLDERNAME, "");
...@@ -120,14 +135,17 @@ class Kopano extends SyncWorker { ...@@ -120,14 +135,17 @@ class Kopano extends SyncWorker {
* Deletes the hidden folder. * Deletes the hidden folder.
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected function DeleteHiddenFolder($folderid) { protected function DeleteHiddenFolder($folderid, $gabId = null, $gabName = 'default') {
$parentfolder = $this->getRootFolder(); $store = $this->getStore($gabId, $gabName);
$parentfolder = $this->getRootFolder($store);
$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid)); $folderentryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($folderid));
if (mapi_last_hresult()) if (mapi_last_hresult())
$this->Terminate(sprintf("Kopano->DeleteHiddenFolder(): Error, could not get PR_ENTRYID for hidden folder: 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->DeleteHiddenFolder(): Error, could not get PR_ENTRYID for hidden folder: 0x%08X", mapi_last_hresult()));
...@@ -141,11 +159,15 @@ class Kopano extends SyncWorker { ...@@ -141,11 +159,15 @@ class Kopano extends SyncWorker {
/** /**
* Returns the internal identifier (folder-id) of the hidden folder. * Returns the internal identifier (folder-id) of the hidden folder.
* *
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
*
* @access protected * @access protected
* @return string|boolean on error * @return string|boolean on error
*/ */
protected function GetHiddenFolderId() { protected function GetHiddenFolderId($gabId = null, $gabName = 'default') {
$parentfolder = $this->getRootFolder(); $store = $this->getStore($gabId, $gabName);
$parentfolder = $this->getRootFolder($store);
$table = mapi_folder_gethierarchytable($parentfolder); $table = mapi_folder_gethierarchytable($parentfolder);
$restriction = array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => PR_DISPLAY_NAME, VALUE => HIDDEN_FOLDERNAME)); $restriction = array(RES_PROPERTY, array(RELOP => RELOP_EQ, ULPROPTAG => PR_DISPLAY_NAME, VALUE => HIDDEN_FOLDERNAME));
...@@ -165,19 +187,22 @@ class Kopano extends SyncWorker { ...@@ -165,19 +187,22 @@ class Kopano extends SyncWorker {
* Removes all messages that have not the same chunkType (chunk configuration changed!). * Removes all messages that have not the same chunkType (chunk configuration changed!).
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected function ClearFolderContents($folderid) { protected function ClearFolderContents($folderid, $gabId = null, $gabName = 'default') {
$this->Log("Kopano->ClearFolderContents: emptying folder"); $this->Log(sprintf("Kopano->ClearFolderContents: emptying folder in GAB '%s': %s", $gabName, $folderid));
$folder = $this->getFolder($folderid); $store = $this->getStore($gabId, $gabName);
$folder = $this->getFolder($store, $folderid);
// empty folder! // empty folder!
$flags = 0; $flags = 0;
mapi_folder_emptyfolder($folder, $flags); mapi_folder_emptyfolder($folder, $flags);
if (mapi_last_hresult()) if (mapi_last_hresult())
$this->Terminate("Kopano->ClearFolderContents: Error, mapi_folder_emptyfolder() failed: 0x%08X"); $this->Terminate(sprintf("Kopano->ClearFolderContents: Error, mapi_folder_emptyfolder() failed on '%s': 0x%08X", $gabName, mapi_last_hresult()));
return true; return true;
} }
...@@ -186,15 +211,18 @@ class Kopano extends SyncWorker { ...@@ -186,15 +211,18 @@ class Kopano extends SyncWorker {
* Removes all messages that do not match the current ChunkType. * Removes all messages that do not match the current ChunkType.
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected function ClearAllNotCurrentChunkType($folderid) { protected function ClearAllNotCurrentChunkType($folderid, $gabId = null, $gabName = 'default') {
$folder = $this->getFolder($folderid); $store = $this->getStore($gabId, $gabName);
$folder = $this->getFolder($store, $folderid);
$table = mapi_folder_getcontentstable($folder); $table = mapi_folder_getcontentstable($folder);
if (!$table) if (!$table)
$this->Terminate(sprintf("Kopano->ClearAllNotCurrentChunkType: Error, unable to read contents table: 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->ClearAllNotCurrentChunkType: Error, unable to read contents table on '%s': 0x%08X", $gabName, mapi_last_hresult()));
$restriction = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => $this->mapiprops['chunktype'], VALUE => $this->chunkType)); $restriction = array(RES_PROPERTY, array(RELOP => RELOP_NE, ULPROPTAG => $this->mapiprops['chunktype'], VALUE => $this->chunkType));
mapi_table_restrict($table, $restriction); mapi_table_restrict($table, $restriction);
...@@ -216,6 +244,23 @@ class Kopano extends SyncWorker { ...@@ -216,6 +244,23 @@ class Kopano extends SyncWorker {
return true; return true;
} }
/**
* Returns a list of Global Address Books with their name and ids.
*
* @access protected
* @return array
*/
protected function GetGABs() {
$names = array();
$companies = mapi_zarafa_getcompanylist($this->store);
if (is_array($companies)) {
foreach($companies as $c) {
$names[$c['companyname']] = bin2hex($c['companyid']);
}
}
return $names;
}
/** /**
* Returns a list with all GAB entries or a single entry specified by $uniqueId. * Returns a list with all GAB entries or a single entry specified by $uniqueId.
* The search for that single entry is done using the configured UNIQUEID parameter. * The search for that single entry is done using the configured UNIQUEID parameter.
...@@ -224,28 +269,38 @@ class Kopano extends SyncWorker { ...@@ -224,28 +269,38 @@ class Kopano extends SyncWorker {
* @param string $uniqueId A value to be found in the configured UNIQUEID. * @param string $uniqueId A value to be found in the configured UNIQUEID.
* If set, only one item is returned. If false or not set, the entire GAB is returned. * If set, only one item is returned. If false or not set, the entire GAB is returned.
* Default: false * Default: false
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
* *
* @access protected * @access protected
* @return array of GABEntry * @return array of GABEntry
*/ */
protected function GetGAB($uniqueId = false) { protected function GetGAB($uniqueId = false, $gabId = null, $gabName = 'default') {
// get all the groups
$groups = mapi_zarafa_getgrouplist($this->store);
$data = array(); $data = array();
$addrbook = mapi_openaddressbook($this->session); $addrbook = mapi_openaddressbook($this->session);
if (mapi_last_hresult()) if (mapi_last_hresult())
$this->Terminate(sprintf("Kopano->GetGAB: Error opening addressbook 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->GetGAB: Error opening addressbook 0x%08X", mapi_last_hresult()));
$ab_entryid = mapi_ab_getdefaultdir($addrbook);
if (mapi_last_hresult()) if ($gabId == null) {
$this->Terminate(sprintf("Kopano->GetGAB: Error, could not get default address directory: 0x%08X", mapi_last_hresult())); $ab_entryid = mapi_ab_getdefaultdir($addrbook);
if (mapi_last_hresult())
$this->Terminate(sprintf("Kopano->GetGAB: Error, could not get '%s' address directory: 0x%08X", $gabName, mapi_last_hresult()));
}
else {
$ab_entryid = hex2bin($gabId);
}
$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid); $ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
if (mapi_last_hresult()) if (mapi_last_hresult())
$this->Terminate(sprintf("Kopano->GetGAB: Error, could not open default address directory: 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->GetGAB: Error, could not open '%s' address directory: 0x%08X", $gabName, mapi_last_hresult()));
$table = mapi_folder_getcontentstable($ab_dir); $table = mapi_folder_getcontentstable($ab_dir);
if (mapi_last_hresult()) if (mapi_last_hresult())
$this->Terminate(sprintf("Kopano->GetGAB: error, could not open addressbook content table: 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->GetGAB: error, could not open '%s' addressbook content table: 0x%08X", $gabName, mapi_last_hresult()));
// get all the groups
$groups = mapi_zarafa_getgrouplist($this->store, $ab_entryid);
// restrict the table if we should only return one // restrict the table if we should only return one
if ($uniqueId) { if ($uniqueId) {
...@@ -261,7 +316,6 @@ class Kopano extends SyncWorker { ...@@ -261,7 +316,6 @@ class Kopano extends SyncWorker {
} }
} }
$gabentries = mapi_table_queryallrows($table, array(PR_ENTRYID, $gabentries = mapi_table_queryallrows($table, array(PR_ENTRYID,
PR_ACCOUNT, PR_ACCOUNT,
PR_DISPLAY_NAME, PR_DISPLAY_NAME,
...@@ -286,6 +340,10 @@ class Kopano extends SyncWorker { ...@@ -286,6 +340,10 @@ class Kopano extends SyncWorker {
PR_DISPLAY_TYPE_EX PR_DISPLAY_TYPE_EX
)); ));
foreach ($gabentries as $entry) { foreach ($gabentries as $entry) {
// do not add SYSTEM user to the GAB
if (strtoupper($entry[PR_DISPLAY_NAME]) == "SYSTEM") {
continue;
}
$a = new GABEntry(); $a = new GABEntry();
$a->type = GABEntry::CONTACT; $a->type = GABEntry::CONTACT;
$a->memberOf = array(); $a->memberOf = array();
...@@ -293,21 +351,29 @@ class Kopano extends SyncWorker { ...@@ -293,21 +351,29 @@ class Kopano extends SyncWorker {
if (is_array($memberOf)) { if (is_array($memberOf)) {
$a->memberOf = array_keys($memberOf); $a->memberOf = array_keys($memberOf);
} }
// the company name is 'Everyone'
if ($gabId != null && $entry[PR_DISPLAY_NAME] == $gabName) {
$entry[PR_ACCOUNT] = "Everyone";
$entry[PR_DISPLAY_NAME] = "Everyone";
}
// is this a group? // is this a group?
if (array_key_exists($entry[PR_ACCOUNT], $groups)) { if (array_key_exists($entry[PR_ACCOUNT], $groups)) {
$a->type = GABEntry::GROUP; $a->type = GABEntry::GROUP;
$users = mapi_zarafa_getuserlistofgroup($this->store, $groups[$entry[PR_ACCOUNT]]['groupid']); $groupentry = mapi_ab_openentry($addrbook, $entry[PR_ENTRYID]);
if (isset($users[$entry[PR_ACCOUNT]]['emailaddress'])) { $grouptable = mapi_folder_getcontentstable($groupentry, MAPI_DEFERRED_ERRORS);
$a->smtpAddress = $users[$entry[PR_ACCOUNT]]['emailaddress']; $users = mapi_table_queryallrows($grouptable, array(PR_ENTRYID, PR_ACCOUNT, PR_SMTP_ADDRESS));
}
$a->members = array(); $a->members = array();
if (is_array($users)) { if (is_array($users)) {
$a->members = array_keys($users); foreach($users as $user) {
// remove the group from itself if ($user[PR_ENTRYID] == $entry[PR_ENTRYID]) {
$key = array_search($entry[PR_ACCOUNT], $a->members); if (isset($user[PR_SMTP_ADDRESS])) {
if ($key !== false) { $a->smtpAddress = $user[PR_SMTP_ADDRESS];
unset($a->members[$key]); }
// don't add the group recursively
continue;
}
$a->members[] = $user[PR_ACCOUNT];
} }
} }
} }
...@@ -353,16 +419,20 @@ class Kopano extends SyncWorker { ...@@ -353,16 +419,20 @@ class Kopano extends SyncWorker {
* @param string $folderid * @param string $folderid
* @param string $chunkName The name of the chunk (used to find the chunk message). * @param string $chunkName The name of the chunk (used to find the chunk message).
* The name is saved in the 'subject' of the chunk message. * The name is saved in the 'subject' of the chunk message.
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
*
* *
* @access protected * @access protected
* @return json string * @return json string
*/ */
protected function GetChunkData($folderid, $chunkName) { protected function GetChunkData($folderid, $chunkName, $gabId = null, $gabName = 'default') {
// find the chunk message in the folder // find the chunk message in the folder
$chunkdata = $this->findChunk($folderid, $chunkName); $store = $this->getStore($gabId, $gabName);
$chunkdata = $this->findChunk($store, $folderid, $chunkName);
if ($chunkdata[PR_ENTRYID]) { if (isset($chunkdata[PR_ENTRYID])) {
$message = mapi_msgstore_openentry($this->store, $chunkdata[PR_ENTRYID]); $message = mapi_msgstore_openentry($store, $chunkdata[PR_ENTRYID]);
return $this->readPropStream($message, PR_BODY); return $this->readPropStream($message, PR_BODY);
} }
else { else {
...@@ -382,20 +452,23 @@ class Kopano extends SyncWorker { ...@@ -382,20 +452,23 @@ class Kopano extends SyncWorker {
* @param string $chunkData The data containing all the data. * @param string $chunkData The data containing all the data.
* @param string $chunkCRC A checksum of the chunk data. To be saved in the 'location' of * @param string $chunkCRC A checksum of the chunk data. To be saved in the 'location' of
* the chunk message. Used to identify changed chunks. * the chunk message. Used to identify changed chunks.
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected function SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC) { protected function SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC, $gabId = null, $gabName = 'default') {
$log = sprintf("Kopano->SetChunkData: %s\tEntries: %d\t Size: %d B\tCRC: %s - ", $chunkName, $amountEntries, strlen($chunkData), $chunkCRC); $log = sprintf("Kopano->SetChunkData: %s\tEntries: %d\t Size: %d B\tCRC: %s - ", $chunkName, $amountEntries, strlen($chunkData), $chunkCRC);
// find the chunk message in the folder // find the chunk message in the folder
$chunkdata = $this->findChunk($folderid, $chunkName); $store = $this->getStore($gabId, $gabName);
$chunkdata = $this->findChunk($store, $folderid, $chunkName);
$message = false; $message = false;
// message not found, create it // message not found, create it
if (empty($chunkdata)) { if (empty($chunkdata)) {
$folder = $this->getFolder($folderid); $folder = $this->getFolder($store, $folderid);
$message = mapi_folder_createmessage($folder); $message = mapi_folder_createmessage($folder);
mapi_setprops($message, array( mapi_setprops($message, array(
PR_MESSAGE_CLASS => "IPM.Appointment", PR_MESSAGE_CLASS => "IPM.Appointment",
...@@ -412,7 +485,7 @@ class Kopano extends SyncWorker { ...@@ -412,7 +485,7 @@ class Kopano extends SyncWorker {
else{ else{
// we need to update the chunk if the CRC does not match! // we need to update the chunk if the CRC does not match!
if ($chunkdata[$this->mapiprops['chunkCRC']] != $chunkCRC) { if ($chunkdata[$this->mapiprops['chunkCRC']] != $chunkCRC) {
$message = mapi_msgstore_openentry($this->store, $chunkdata[PR_ENTRYID]); $message = mapi_msgstore_openentry($store, $chunkdata[PR_ENTRYID]);
$log .= "opening - "; $log .= "opening - ";
} }
else { else {
...@@ -427,8 +500,13 @@ class Kopano extends SyncWorker { ...@@ -427,8 +500,13 @@ class Kopano extends SyncWorker {
PR_BODY => $chunkData, PR_BODY => $chunkData,
$this->mapiprops['updatetime'] => time(), $this->mapiprops['updatetime'] => time(),
)); ));
mapi_savechanges($message); @mapi_savechanges($message);
$log .= "saved"; if (mapi_last_hresult()) {
$log .= sprintf("error saving: 0x%08X", mapi_last_hresult());
}
else {
$log .= "saved";
}
} }
// output log // output log
...@@ -444,15 +522,16 @@ class Kopano extends SyncWorker { ...@@ -444,15 +522,16 @@ class Kopano extends SyncWorker {
/** /**
* Finds the chunk and returns a property array. * Finds the chunk and returns a property array.
* *
* @param ressoure $store
* @param string $folderid * @param string $folderid
* @param string $chunkName * @param string $chunkName
* *
* @access private * @access private
* @return array * @return array
*/ */
private function findChunk($folderid, $chunkName) { private function findChunk($store, $folderid, $chunkName) {
// search for the chunk message // search for the chunk message
$folder = $this->getFolder($folderid); $folder = $this->getFolder($store, $folderid);
$table = mapi_folder_getcontentstable($folder); $table = mapi_folder_getcontentstable($folder);
if (!$table) if (!$table)
$this->Log(sprintf("Kopano->findChunk: Error, unable to read contents table to find chunk '%d': 0x%08X", $chunkId, mapi_last_hresult())); $this->Log(sprintf("Kopano->findChunk: Error, unable to read contents table to find chunk '%d': 0x%08X", $chunkId, mapi_last_hresult()));
...@@ -520,26 +599,53 @@ class Kopano extends SyncWorker { ...@@ -520,26 +599,53 @@ class Kopano extends SyncWorker {
return $store; return $store;
} }
/**
* Returns the store for a gab id and name
*
* @param string $gabId
* @param string $gabName
*
* @access private
* @return ressource
*/
private function getStore($gabId, $gabName) {
if (!$gabId) {
return $this->store;
}
if (!isset($this->storeCache[$gabId])) {
$user = (strtoupper($this->targetStore) == 'SYSTEM') ? $gabName : $this->targetStore . "@" . $gabName;
$store_entryid = mapi_msgstore_createentryid($this->store, $user);
$store = mapi_openmsgstore($this->session, $store_entryid);
$this->Log(sprintf("Kopano->getStore(): Found store of user '%s': '%s'", $user, $store));
$this->storeCache[$gabId] = $store;
}
return $this->storeCache[$gabId];
}
/** /**
* Opens the root folder, either in a user's store or of the public folder. * Opens the root folder, either in a user's store or of the public folder.
* *
* @param ressource $store
*
* @access private * @access private
* @return ressource * @return ressource
*/ */
private function getRootFolder() { private function getRootFolder($store) {
$rootId = "root"; $rootId = "root";
if (!isset($this->folderCache[$rootId])) { if (!isset($this->folderCache[$rootId])) {
$parentfentryid = false; $parentfentryid = false;
// the default store root // the default store root
if ($this->mainUser == HIDDEN_FOLDERSTORE) { if ($this->mainUser == HIDDEN_FOLDERSTORE && strtoupper($this->mainUser) != "SYSTEM") {
$parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID)); $parentprops = mapi_getprops($store, array(PR_IPM_SUBTREE_ENTRYID));
if (isset($parentprops[PR_IPM_SUBTREE_ENTRYID])) if (isset($parentprops[PR_IPM_SUBTREE_ENTRYID]))
$parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID]; $parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID];
} }
// get the main public folder // get the main public folder
else { else {
$parentprops = mapi_getprops($this->store, array(PR_IPM_PUBLIC_FOLDERS_ENTRYID)); $parentprops = mapi_getprops($store, array(PR_IPM_PUBLIC_FOLDERS_ENTRYID));
if (isset($parentprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID])) if (isset($parentprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]))
$parentfentryid = $parentprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID]; $parentfentryid = $parentprops[PR_IPM_PUBLIC_FOLDERS_ENTRYID];
} }
...@@ -547,7 +653,7 @@ class Kopano extends SyncWorker { ...@@ -547,7 +653,7 @@ class Kopano extends SyncWorker {
if (!$parentfentryid) if (!$parentfentryid)
$this->Terminate(sprintf("Kopano->getRootFolder(): Error, unable to open parent folder (no entry id): 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->getRootFolder(): Error, unable to open parent folder (no entry id): 0x%08X", mapi_last_hresult()));
$parentfolder = mapi_msgstore_openentry($this->store, $parentfentryid); $parentfolder = mapi_msgstore_openentry($store, $parentfentryid);
if (!$parentfolder) if (!$parentfolder)
$this->Terminate(sprintf("Kopano->getRootFolder(): Error, unable to open parent folder (open entry): 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->getRootFolder(): Error, unable to open parent folder (open entry): 0x%08X", mapi_last_hresult()));
...@@ -559,17 +665,19 @@ class Kopano extends SyncWorker { ...@@ -559,17 +665,19 @@ class Kopano extends SyncWorker {
/** /**
* Opens a folder. * Opens a folder.
* *
* @param string $folderid * @param ressource $store
* @param string $folderid
*
* @access private * @access private
* @return ressource * @return ressource
*/ */
private function getFolder($folderid) { private function getFolder($store, $folderid) {
if (!isset($this->folderCache[$folderid])) { if (!isset($this->folderCache[$folderid])) {
$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid)); $folderentryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($folderid));
if (!$folderentryid) if (!$folderentryid)
$this->Terminate(sprintf("Kopano->getFolder: Error, unable to open folder (no entry id): 0x%08X", mapi_last_hresult())); $this->Terminate(sprintf("Kopano->getFolder(): Error, unable to open folder (no entry id): 0x%08X", mapi_last_hresult()));
$this->folderCache[$folderid] = mapi_msgstore_openentry($this->store, $folderentryid); $this->folderCache[$folderid] = mapi_msgstore_openentry($store, $folderentryid);
} }
return $this->folderCache[$folderid]; return $this->folderCache[$folderid];
...@@ -630,4 +738,30 @@ class Kopano extends SyncWorker { ...@@ -630,4 +738,30 @@ class Kopano extends SyncWorker {
return $string; return $string;
} }
/**
* Checks if the PHP-MAPI extension is available and in a requested version.
*
* @param string $version the version to be checked ("6.30.10-18495", parts or build number)
*
* @access private
* @return boolean installed version is superior to the checked string
*/
private function checkMapiExtVersion($version = "") {
// compare build number if requested
if (preg_match('/^\d+$/', $version) && strlen($version) > 3) {
$vs = preg_split('/-/', phpversion("mapi"));
return ($version <= $vs[1]);
}
if (extension_loaded("mapi")){
if (version_compare(phpversion("mapi"), $version) == -1){
return false;
}
}
else
return false;
return true;
}
} }
\ No newline at end of file
...@@ -58,12 +58,14 @@ abstract class SyncWorker { ...@@ -58,12 +58,14 @@ abstract class SyncWorker {
/** /**
* Simulates the synchronization, showing statistics but without touching any data. * Simulates the synchronization, showing statistics but without touching any data.
* *
* @param string $targetGab
*
* @access public * @access public
* @return void * @return void
*/ */
public function Simulate() { public function Simulate($targetGab) {
$this->Log("Simulating the synchronization. NO DATA IS GOING TO BE WRITTEN.".PHP_EOL); $this->Log("Simulating the synchronization. NO DATA IS GOING TO BE WRITTEN.".PHP_EOL);
$this->Sync(false); $this->Sync($targetGab, false);
} }
/** /**
...@@ -76,22 +78,54 @@ abstract class SyncWorker { ...@@ -76,22 +78,54 @@ abstract class SyncWorker {
* - sends it to SetChunkData() to be written * - sends it to SetChunkData() to be written
* - shows some stats * - shows some stats
* *
* @param string $targetGab the gab name id that should be synchronized, if not set 'default' or all are used.
* @param string $doWrite if set to false, no data will be written (simulated mode). Default: true. * @param string $doWrite if set to false, no data will be written (simulated mode). Default: true.
*
* @access public * @access public
* @return void * @return void
*/ */
public function Sync($doWrite = true) { public function Sync($targetGab = false, $doWrite = true) {
// gets a list of GABs
$gabs = $this->GetGABs();
if (empty($gabs)) {
if($targetGab) {
$this->Terminate("Multiple GABs not found, target should not be set. Aborting.");
}
// no multi-GABs, just go default
$this->doSync(null, 'default', $doWrite);
}
else {
foreach($gabs as $gabName => $gabId) {
if (!$targetGab || $targetGab == $gabName || $targetGab == $gabId) {
$this->doSync($gabId, $gabName, $doWrite);
}
}
}
}
/**
* Performs the actual synchronization for a single GAB.
*
* @param string $gabId the id of the gab to be synchronized. If not set (null) the default gab is synchronized.
* @param string $gabName the name of the gab to be synchronized. If not set (null) the default gab is synchronized.
* @param string $doWrite if set to false, no data will be written (simulated mode). Default: true.
*
* @access private
* @return void
*/
private function doSync($gabId = null, $gabName = 'default', $doWrite = true) {
// get the folderid of the hidden folder - will be created if not yet available // get the folderid of the hidden folder - will be created if not yet available
$folderid = $this->getFolderId($doWrite); $folderid = $this->getFolderId($gabId, $gabName, $doWrite);
$this->Log(sprintf("Starting %sGAB sync to store '%s' on id '%s'", (!$doWrite?"simulated ":""), HIDDEN_FOLDERSTORE, $folderid)); $this->Log(sprintf("Starting %sGAB sync to store '%s' %s on id '%s'", (!$doWrite?"simulated ":""), HIDDEN_FOLDERSTORE, ($gabId?"of '".$gabName."'":""), $folderid));
// remove all messages that do not match the current $chunkType // remove all messages that do not match the current $chunkType
if ($doWrite) if ($doWrite)
$this->ClearAllNotCurrentChunkType($folderid); $this->ClearAllNotCurrentChunkType($folderid, $gabId, $gabName);
// get all GAB entries // get all GAB entries
$gab = $this->GetGAB(); $gab = $this->GetGAB(false, $gabId, $gabName);
// build the chunks // build the chunks
$chunks = array(); $chunks = array();
...@@ -136,7 +170,7 @@ abstract class SyncWorker { ...@@ -136,7 +170,7 @@ abstract class SyncWorker {
if ($doWrite) { if ($doWrite) {
$chunkName = $this->chunkType . "/". $chunkId; $chunkName = $this->chunkType . "/". $chunkId;
$chunkCRC = md5($chunkData); $chunkCRC = md5($chunkData);
$this->SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC); $this->SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC, $gabId, $gabName);
} }
} }
...@@ -157,15 +191,37 @@ abstract class SyncWorker { ...@@ -157,15 +191,37 @@ abstract class SyncWorker {
* Updates a single entry of the GAB in the respective chunk. * Updates a single entry of the GAB in the respective chunk.
* *
* @param string $uniqueId * @param string $uniqueId
* @param string $targetGab
* *
* @access public * @access public
* @return void * @return void
*/ */
public function SyncOne($uniqueId) { public function SyncOne($uniqueId, $targetGab) {
$this->Log(sprintf("Sync-one: %s = '%s'", UNIQUEID, $uniqueId)); $this->Log(sprintf("Sync-one: %s = '%s'%s", UNIQUEID, $uniqueId, ($targetGab) ? " of '".$targetGab."'":''));
// gets a list of GABs
$gabs = $this->GetGABs();
if (empty($gabs)) {
if($targetGab) {
$this->Terminate("Multiple GABs not found, target should not be set. Aborting.");
}
// default case, no multi-GABs, just go default
$gabId = null;
$gabName = 'default';
}
else {
foreach($gabs as $testGabName => $testGabId) {
if ($targetGab == $testGabName || $targetGab == $testGabId) {
$gabId = $testGabId;
$gabName = $testGabName;
break;
}
}
}
// search for the entry in the GAB // search for the entry in the GAB
$entries = $this->GetGAB($uniqueId); $entries = $this->GetGAB($uniqueId, $gabId, $gabName);
// if an entry is found, update the chunk // if an entry is found, update the chunk
// if the entry is NOT found, we should remove it from the chunk (entry deleted) // if the entry is NOT found, we should remove it from the chunk (entry deleted)
...@@ -182,10 +238,10 @@ abstract class SyncWorker { ...@@ -182,10 +238,10 @@ abstract class SyncWorker {
} }
// get the data for the chunkId // get the data for the chunkId
$folderid = $this->getFolderId(); $folderid = $this->getFolderId($gabId, $gabName);
$chunkId = $this->calculateChunkId($key); $chunkId = $this->calculateChunkId($key);
$chunkName = $this->chunkType . "/". $chunkId; $chunkName = $this->chunkType . "/". $chunkId;
$chunkdata = $this->GetChunkData($folderid, $chunkName); $chunkdata = $this->GetChunkData($folderid, $chunkName, $gabId, $gabName);
$chunk = json_decode($chunkdata, true); $chunk = json_decode($chunkdata, true);
// update or remove the entry // update or remove the entry
...@@ -213,50 +269,119 @@ abstract class SyncWorker { ...@@ -213,50 +269,119 @@ abstract class SyncWorker {
// update the chunk data // update the chunk data
$chunkCRC = md5($chunkData); $chunkCRC = md5($chunkData);
$status = $this->SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC); $status = $this->SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC, $gabId, $gabName);
if ($status) { if ($status) {
$this->Log("Success!"); $this->Log("Success!");
} }
} }
/** /**
* Clears all data from the hidden folder (without removing it). * Clears all data from the hidden folder (without removing it) for a target GAB.
* This will cause a serverside clearing of all user gabs. * This will cause a serverside clearing of all user gabs.
* *
* @param string $targetGab A gab where the data should be cleared. If not set, it's 'default' or all.
*
* @access public * @access public
* @return void * @return void
*/ */
public function ClearAll() { public function ClearAll($targetGab) {
$folderid = $this->GetHiddenFolderId(); // gets a list of GABs
$gabs = $this->GetGABs();
if (empty($gabs)) {
if($targetGab) {
$this->Terminate("Multiple GABs not found, target should not be set. Aborting.");
}
// no multi-GABs, just go default
$this->doClearAll(null, 'default');
}
else {
foreach($gabs as $gabName => $gabId) {
if (!$targetGab || $targetGab == $gabName || $targetGab == $gabId) {
$this->doClearAll($gabId, $gabName);
}
}
}
}
/**
* Clears all data from the hidden folder (without removing it) for a specific gabId and gabName.
* This will cause a serverside clearing of all user gabs.
*
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
*
* @access private
* @return boolean
*/
private function doClearAll($gabId = null, $gabName = 'default') {
$folderid = $this->GetHiddenFolderId($gabId, $gabName);
if (!$folderid) { if (!$folderid) {
$this->Terminate("Could not locate folder. Aborting."); $this->Log(sprintf("Could not locate folder in '%s'. Aborting.", $gabName));
return false;
} }
$status = $this->ClearFolderContents($folderid); $status = $this->ClearFolderContents($folderid, $gabId, $gabName);
if ($status) { if ($status) {
$this->Log("Success!"); $this->Log(sprintf("Success for '%s'!", $gabName));
} }
return !!$status;
} }
/** /**
* Clears all data from the hidden folder and removes it. * Clears all data from the hidden folder and removes it.
* This will cause a serverside clearing of all user gabs and a synchronization stop. * This will cause a serverside clearing of all user gabs and a synchronization stop.
* *
* @param string $targetGab A gab where the data should be cleared. If not set, it's 'default' or all.
*
* @access public * @access public
* @return void * @return void
*/ */
public function DeleteAll() { public function DeleteAll($targetGab) {
$folderid = $this->GetHiddenFolderId(); // gets a list of GABs
$gabs = $this->GetGABs();
if (empty($gabs)) {
if($targetGab) {
$this->Terminate("Multiple GABs not found, target should not be set. Aborting.");
}
// no multi-GABs, just go default
$this->doDeleteAll(null, 'default');
}
else {
foreach($gabs as $gabName => $gabId) {
if (!$targetGab || $targetGab == $gabName || $targetGab == $gabId) {
$this->doDeleteAll($gabId, $gabName);
}
}
}
}
/**
* Clears all data from the hidden folder and removes it for the gab id and Name.
* This will cause a serverside clearing of all user gabs and a synchronization stop.
*
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
*
* @access private
* @return boolean
*/
private function doDeleteAll($gabId = null, $gabName = 'default') {
$folderid = $this->GetHiddenFolderId($gabId, $gabName);
if (!$folderid) { if (!$folderid) {
$this->Terminate("Could not locate folder. Aborting."); $this->Log(sprintf("Could not locate folder in '%s'", $gabName));
return false;
} }
$emptystatus = $this->ClearFolderContents($folderid); $emptystatus = $this->ClearFolderContents($folderid, $gabId, $gabName);
if ($emptystatus) { if ($emptystatus) {
$status = $this->DeleteHiddenFolder($folderid); $status = $this->DeleteHiddenFolder($folderid, $gabId, $gabName);
if ($status) { if ($status) {
$this->Log("Success!"); $this->Log(sprintf("Success for '%s'!", $gabName));
return true;
} }
} }
return false;
} }
/** /**
...@@ -289,16 +414,18 @@ abstract class SyncWorker { ...@@ -289,16 +414,18 @@ abstract class SyncWorker {
* Gets the ID of the hidden folder. If $doCreate is true (or not set) the * Gets the ID of the hidden folder. If $doCreate is true (or not set) the
* folder will be created if it does not yet exist. * folder will be created if it does not yet exist.
* *
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param boolean $doCreate Creates the folder if it does not exist, default: true * @param boolean $doCreate Creates the folder if it does not exist, default: true
* *
* @access protected * @access protected
* @return string * @return string
*/ */
protected function getFolderId($doCreate = true) { protected function getFolderId($gabId = null, $gabName = 'default', $doCreate = true) {
$id = $this->GetHiddenFolderId(); $id = $this->GetHiddenFolderId($gabId, $gabName);
if (!$id) { if (!$id) {
if ($doCreate) if ($doCreate)
$id = $this->CreateHiddenFolder(); $id = $this->CreateHiddenFolder($gabId, $gabName);
else else
$id = "<does not yet exist>"; $id = "<does not yet exist>";
} }
...@@ -345,61 +472,84 @@ abstract class SyncWorker { ...@@ -345,61 +472,84 @@ abstract class SyncWorker {
/** /**
* Creates the hidden folder. * Creates the hidden folder.
* *
* @param string $gabId the id of the gab where the hidden folder should be created. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be created. If not set (null) the default gab is used.
*
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected abstract function CreateHiddenFolder(); protected abstract function CreateHiddenFolder($gabId = null, $gabName = 'default');
/** /**
* Deletes the hidden folder. * Deletes the hidden folder.
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected abstract function DeleteHiddenFolder($folderid); protected abstract function DeleteHiddenFolder($folderid, $gabId = null, $gabName = 'default');
/** /**
* Returns the internal identifier (folder-id) of the hidden folder. * Returns the internal identifier (folder-id) of the hidden folder.
* *
* @param string $gabId the id of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be searched. If not set (null) the default gab is used.
*
* @access protected * @access protected
* @return string * @return string
*/ */
protected abstract function GetHiddenFolderId(); protected abstract function GetHiddenFolderId($gabId = null, $gabName = 'default');
/** /**
* Removes all messages that have not the same chunkType (chunk configuration changed!) * Removes all messages that have not the same chunkType (chunk configuration changed!).
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected abstract function ClearFolderContents($folderid); protected abstract function ClearFolderContents($folderid, $gabId = null, $gabName = 'default');
/** /**
* Removes all messages that do not match the current ChunkType. * Removes all messages that do not match the current ChunkType.
* *
* @param string $folderid * @param string $folderid
* @param string $gabId the id of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* @param string $gabName the name of the gab where the hidden folder should be cleared. If not set (null) the default gab is used.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected abstract function ClearAllNotCurrentChunkType($folderid); protected abstract function ClearAllNotCurrentChunkType($folderid, $gabId = null, $gabName = 'default');
/**
* Returns a list of Global Address Books with their name and ids.
*
* @access protected
* @return array
*/
protected abstract function GetGABs();
/** /**
* Returns a list with all GAB entries or a single entry specified by $uniqueId. * Returns a list with all GAB entries or a single entry specified by $uniqueId.
* The search for that single entry is done using the configured UNIQUEID parameter. * The search for that single entry is done using the configured UNIQUEID parameter.
* If no entry is found for a $uniqueId an empty array() must be returned.
* *
* @param string $uniqueId A value to be found in the configured UNIQUEID. * @param string $uniqueId A value to be found in the configured UNIQUEID.
* If set, only one item is returned. If false or not set, the entire GAB is returned. * If set, only one item is returned. If false or not set, the entire GAB is returned.
* Default: false * Default: false
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
* *
* @access protected * @access protected
* @return array of GABEntry * @return array of GABEntry
*/ */
protected abstract function GetGAB($uniqueId = false); protected abstract function GetGAB($uniqueId = false, $gabId = null, $gabName = 'default');
/** /**
* Returns the chunk data of the chunkId of the hidden folder. * Returns the chunk data of the chunkId of the hidden folder.
...@@ -407,11 +557,14 @@ abstract class SyncWorker { ...@@ -407,11 +557,14 @@ abstract class SyncWorker {
* @param string $folderid * @param string $folderid
* @param string $chunkName The name of the chunk (used to find the chunk message). * @param string $chunkName The name of the chunk (used to find the chunk message).
* The name is saved in the 'subject' of the chunk message. * The name is saved in the 'subject' of the chunk message.
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
*
* *
* @access protected * @access protected
* @return json string * @return json string
*/ */
protected abstract function GetChunkData($folderid, $chunkName); protected abstract function GetChunkData($folderid, $chunkName, $gabId = null, $gabName = 'default');
/** /**
* Updates the chunk data in the hidden folder if it changed. * Updates the chunk data in the hidden folder if it changed.
...@@ -424,9 +577,11 @@ abstract class SyncWorker { ...@@ -424,9 +577,11 @@ abstract class SyncWorker {
* @param string $chunkData The data containing all the data. * @param string $chunkData The data containing all the data.
* @param string $chunkCRC A checksum of the chunk data. To be saved in the 'location' of * @param string $chunkCRC A checksum of the chunk data. To be saved in the 'location' of
* the chunk message. Used to identify changed chunks. * the chunk message. Used to identify changed chunks.
* @param string $gabId Id that uniquely identifies the GAB. If not set or null the default GAB is assumed.
* @param string $gabName String that uniquely identifies the GAB. If not set the default GAB is assumed.
* *
* @access protected * @access protected
* @return boolean * @return boolean
*/ */
protected abstract function SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC); protected abstract function SetChunkData($folderid, $chunkName, $amountEntries, $chunkData, $chunkCRC, $gabId = null, $gabName = 'default');
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment