function get($lastmodified, $api) { $sync = new Sync(); $output = array(); if ($lastmodified < $sync->get(Sync::USER)) { $output[Sync::USER] = getUser($api); } if ($lastmodified < $sync->get(Sync::SCENARIOS)) { $output[Sync::SCENARIOS] = getScenarios(); } if ($lastmodified < $sync->get(Sync::LIGHTS)) { $output[Sync::LIGHTS] = getLights(); } if ($lastmodified < $sync->get(Sync::ROOMS)) { $output[Sync::ROOMS] = getRooms(); } if ($lastmodified < $sync->get(Sync::PLANTS)) { $output[Sync::PLANTS] = getPlants(); } if ($lastmodified < $sync->get(Sync::DEVICES)) { $output[Sync::DEVICES] = getDevices(); } if ($lastmodified < $sync->get(Sync::SENSORS)) { $output[Sync::SENSORS] = getSensors(); } if ($lastmodified < $sync->get(Sync::EAN)) { $output[Sync::EAN] = getEan(); } return $output; }
public static function invokeService() { $request = file_get_contents('php://input'); $request = json_decode($request); $userId = self::get_user_id($request->token); if (empty($userId)) { $response = new CliniqueServiceResponce(); $response->response(true, 'Invalid user'); die; } switch ($request->type) { case 'bookmark': Sync::syncBookmarks($userId, $request->data); break; case 'notes': Sync::syncNotes($userId, $request->data); break; case 'favorite': Sync::syncFavorites($userId, $request->data); break; case 'badges': Sync::syncBadges($userId, $request->data); break; case 'quiz': Sync::syncQuiz($userId, $request); break; case 'scorm': Sync::syncScorm($userId, $request); break; case 'completion': Sync::syncCompletion($userId, $request); break; } }
/** * Gets called when the script is exiting. * Sends out all sync records. */ public static function exiting() { // Use all of the course records foreach (self::$coursesToSync as $course) { // Sync the course $course->perform_sync(); } self::$coursesToSync = array(); }
public function testSynchronizationFlow() { $session = ['id' => $this->sessionId, 'queues' => ['data' => ['name' => 'main', 'pages' => 1, 'total_count' => 2], 'meta' => ['type' => 'sync_queue']]]; $queueItems = [['data' => ['id' => 1], 'meta' => ['type' => 'user', 'sync' => ['event_type' => 'created', 'ack_key' => 'User-1234-1', 'revision' => 1]]], ['data' => ['id' => 1], 'meta' => ['type' => 'source', 'sync' => ['event_type' => 'created', 'ack_key' => 'Source-1234-1', 'revision' => 1]]]]; $ackKeys = array_map(function ($item) { return $item['meta']['sync']['ack_key']; }, $queueItems); $client = $this->getMockBuilder('\\BaseCRM\\Client')->disableOriginalConstructor()->getMock(); $syncService = $this->getMockBuilder('\\BaseCRM\\SyncService')->disableOriginalConstructor()->getMock(); $syncService->expects($this->once())->method('start')->with($this->deviceUUID)->will($this->returnValue($session)); $syncService->expects($this->exactly(2))->method('fetch')->with($this->deviceUUID, $session['id'])->will($this->onConsecutiveCalls($queueItems, [])); $syncService->expects($this->at(1))->method('fetch')->with($this->deviceUUID, $session['id'])->will($this->returnValue([])); $client->sync = $syncService; $sync = new Sync($client, $this->deviceUUID); $counter = 0; $sync->fetch(function ($meta, $data) use(&$counter) { $this->assertTrue(isset($meta['sync'])); $this->assertEquals($data['id'], 1); $counter += 1; }); $this->assertEquals($counter, 2); }
function sync_controller() { global $mysqli, $session, $route; include "Modules/feed/feed_model.php"; $feed = new Feed($mysqli); include "Modules/sync/sync_model.php"; $sync = new Sync($mysqli, $feed, $session['userid']); if ($route->format == 'html') { if ($route->action == "list" && $session['write']) { $result = view("Modules/sync/sync_view.php", array()); } } if ($route->format == 'json') { // Register a feed to be downloaded if ($route->action == "feed" && $session['write']) { $result = $sync->add_feed($session['userid'], get('feedid'), get('name'), get('datatype')); } // Save remote url and apikey if ($route->action == "setsettings" && $session['write']) { $result = $sync->set_settings($session['userid'], get('remoteurl'), get('remotekey')); } // Save remote url and apikey if ($route->action == "getsettings" && $session['write']) { $result = $sync->get_settings($session['userid']); } // get the remote feed list, we want to load the remote feeds when the page is first loaded // but only queue progress updates there after if ($route->action == "getremotefeeds" && $session['write']) { $result = $sync->get_remote_feeds($session['userid']); } if ($route->action == "getimportqueue" && $session['write']) { $result = $sync->get_importqueue($session['userid']); } if ($route->action == "getlocalfeeds" && $session['write']) { $result = $sync->get_local_feeds($session['userid']); } } return array('content' => $result); }
/** * Gets called when this entry changes. */ public function changed() { Sync::course(Course::fromId($this->getCourseId())); }
public function testSettings() { $settingKey = 'tagColors'; $value = array(array("name" => "_READ", "color" => "#990000")); $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $lastSyncTimestamp = (int) $xml['timestamp']; $libraryVersion = API::getLibraryVersion(); // Create item via sync $data = '<data version="9"><settings><setting libraryID="' . self::$config['libraryID'] . '" name="' . $settingKey . '">' . htmlspecialchars(json_encode($value)) . '</setting></settings></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); // Check via sync $xml = Sync::updated(self::$sessionID, $lastSyncTimestamp); $updateKey = (string) $xml['updateKey']; $lastSyncTimestamp = $xml['timestamp']; $settingXML = $xml->updated[0]->settings[0]->setting[0]; $this->assertEquals(self::$config['libraryID'], (int) $settingXML['libraryID']); $this->assertEquals($settingKey, (string) $settingXML['name']); $this->assertEquals($value, json_decode((string) $settingXML, true)); // Get setting via API and check value $response = API::userGet(self::$config['userID'], "settings/{$settingKey}?key=" . self::$config['apiKey']); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertNotNull($json); $this->assertEquals($value, $json['value']); $this->assertEquals($libraryVersion + 1, $json['version']); // Delete via sync $xmlstr = '<data version="9">' . '<deleted>' . '<settings>' . '<setting libraryID="' . self::$config['libraryID'] . '" key="' . $settingKey . '"/>' . '</settings>' . '</deleted>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); $xml = Sync::waitForUpload(self::$sessionID, $response, $this); // Get setting via API and check value $response = API::userGet(self::$config['userID'], "settings/{$settingKey}?key=" . self::$config['apiKey']); $this->assertEquals(404, $response->getStatus()); // Check for missing via sync $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $lastSyncTimestamp = $xml['timestamp']; $this->assertEquals(0, $xml->updated[0]->settings->count()); $this->assertEquals(1, $xml->updated[0]->deleted[0]->settings[0]->setting->count()); $this->assertEquals(self::$config['libraryID'], (int) $xml->updated[0]->deleted[0]->settings[0]->setting[0]['libraryID']); $this->assertEquals($settingKey, (string) $xml->updated[0]->deleted[0]->settings[0]->setting[0]['key']); }
private function _testDeleteAndDeleted($objectType) { API::userClear(self::$config['userID']); $objectTypePlural = API::getPluralObjectType($objectType); $xml = Sync::updated(self::$sessionID); $lastSyncTimestamp = (int) $xml['timestamp']; // Create via sync switch ($objectType) { case 'item': $keys[] = Sync::createItem(self::$sessionID, self::$config['libraryID'], "book", false, $this); break; case 'setting': $settingKey = "tagColors"; $response = API::userPut(self::$config['userID'], "settings/{$settingKey}?key=" . self::$config['apiKey'], json_encode(array("value" => array(array("name" => "_READ", "color" => "#990000")))), array("Content-Type: application/json", "If-Unmodified-Since-Version: 0")); $this->assertEquals(204, $response->getStatus()); $keys[] = $settingKey; break; } // Check via API foreach ($keys as $key) { $response = API::userGet(self::$config['userID'], "{$objectTypePlural}/{$key}?key=" . self::$config['apiKey']); $this->assertEquals(200, $response->getStatus()); $version = $response->getHeader("Last-Modified-Version"); $this->assertNotNull($version); } // Get empty deleted via API $response = API::userGet(self::$config['userID'], "deleted?key=" . self::$config['apiKey'] . "&newer={$version}"); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertEmpty($json[$objectTypePlural]); // Get empty deleted via API with newertime $response = API::userGet(self::$config['userID'], "deleted?key=" . self::$config['apiKey'] . "&newertime={$lastSyncTimestamp}"); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertEmpty($json[$objectTypePlural]); // Delete via sync foreach ($keys as $key) { switch ($objectType) { case 'item': Sync::deleteItem(self::$sessionID, self::$config['libraryID'], $key, $this); break; case 'setting': // Delete via sync $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $xmlstr = '<data version="9">' . '<deleted>' . '<settings>' . '<setting libraryID="' . self::$config['libraryID'] . '" key="' . $key . '"/>' . '</settings>' . '</deleted>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); break; } } // Check 404 via API foreach ($keys as $key) { $response = API::userGet(self::$config['userID'], "{$objectTypePlural}/{$key}?key=" . self::$config['apiKey']); $this->assertEquals(404, $response->getStatus()); } // Get deleted via API $response = API::userGet(self::$config['userID'], "deleted?key=" . self::$config['apiKey'] . "&newer={$version}"); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertArrayHasKey($objectTypePlural, $json); $this->assertCount(sizeOf($keys), $json[$objectTypePlural]); foreach ($keys as $key) { $this->assertContains($key, $json[$objectTypePlural]); } // Get deleted via API with newertime $response = API::userGet(self::$config['userID'], "deleted?key=" . self::$config['apiKey'] . "&newertime={$lastSyncTimestamp}"); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertArrayHasKey($objectTypePlural, $json); $this->assertCount(sizeOf($keys), $json[$objectTypePlural]); foreach ($keys as $key) { $this->assertContains($key, $json[$objectTypePlural]); } // Should be empty with later newertime $xml = Sync::updated(self::$sessionID); $lastSyncTimestamp = (int) $xml['timestamp']; $response = API::userGet(self::$config['userID'], "deleted?key=" . self::$config['apiKey'] . "&newertime=" . ($lastSyncTimestamp + 2)); $this->assertEquals(200, $response->getStatus()); $json = json_decode($response->getBody(), true); $this->assertEmpty($json[$objectTypePlural]); }
public function testEmptyCreator() { $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; // Create creator via sync $data = '<data version="9"><creators>' . '<creator libraryID="' . self::$config['libraryID'] . '" ' . 'key="AAAAAAAA" dateAdded="2013-12-01 03:53:20" dateModified="2013-12-01 03:54:09">' . '<name></name>' . '<fieldMode>1</fieldMode>' . '</creator>' . '<creator libraryID="' . self::$config['libraryID'] . '" ' . 'key="BBBBBBBB" dateAdded="2013-12-01 04:53:20" dateModified="2013-12-01 04:54:09">' . '<name>' . chr(0xef) . chr(0xbb) . chr(0xbf) . '</name>' . '<fieldMode>1</fieldMode>' . '</creator>' . '</creators></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); // Creators should have been skipped $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $this->assertEquals(0, sizeOf($xml->updated->creators->creator)); // Create creator with valid name $data = '<data version="9"><creators>' . '<creator libraryID="' . self::$config['libraryID'] . '" ' . 'key="AAAAAAAA" dateAdded="2013-12-01 03:53:20" dateModified="2013-12-01 03:54:09">' . '<name>Test</name>' . '<fieldMode>1</fieldMode>' . '</creator>' . '</creators></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $this->assertEquals(1, sizeOf($xml->updated->creators->creator)); // Update with empty $data = '<data version="9"><creators>' . '<creator libraryID="' . self::$config['libraryID'] . '" ' . 'key="AAAAAAAA" dateAdded="2013-12-01 03:53:20" dateModified="2013-12-01 03:54:09">' . '<name>' . chr(0xef) . chr(0xbb) . chr(0xbf) . '</name>' . '<fieldMode>1</fieldMode>' . '</creator>' . '</creators></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $this->assertEquals(1, sizeOf($xml->updated->creators->creator)); // Not ideal, but for now the updated creator should just be ignored $this->assertEquals("Test", (string) $xml->updated->creators->creator->name); }
public function testCheckFieldStatusReturnBoolean() { $data = Sync::checkFieldStatus('Contact'); $this->assertInternalType('bool', $data); }
public function testGroupDeleteItemLibraryAccessDenied() { // Create item $xml = Sync::updated(self::$sessionID2); $updateKey = (string) $xml['updateKey']; $data = '<data version="9"><items><item libraryID="' . self::$config['ownedPrivateGroupLibraryID2'] . '" itemType="note" ' . 'dateAdded="2009-03-07 04:53:20" ' . 'dateModified="2009-03-07 04:54:09" ' . 'key="AAAAAAAA"><note>Test</note></item></items></data>'; $response = Sync::upload(self::$sessionID2, $updateKey, $data, true); Sync::waitForUpload(self::$sessionID2, $response, $this); // Delete item without permissions $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $data = '<data version="9"><deleted><items><item libraryID="' . self::$config['ownedPrivateGroupLibraryID2'] . '" key="AAAAAAAA"/>' . "</items></deleted></data>"; $response = Sync::upload(self::$sessionID, $updateKey, $data, true); $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true); $this->assertTrue(isset($xml->error)); $this->assertEquals("LIBRARY_ACCESS_DENIED", $xml->error["code"]); }
} else { return FALSE; } return TRUE; } } $opts = getopt("c:p:a:u:g:h"); if ($opts === FALSE) { exit(1); } if (!empty($opts["h"])) { echo "Usage: {$argv[0]} [ -c config.php ] [ -p sync.pid ] [ -u uid ] [ -g gid ] [ -a start|stop ]\n"; exit(0); } $configPath = isset($opts["c"]) ? $opts["c"] : dirname(__FILE__) . "/config.php"; $sync = new Sync($configPath, !empty($opts["p"]) ? $opts["p"] : null); if (!empty($opts["g"])) { $gid = $opts["g"]; if (posix_setgid($gid) === FALSE) { syslog(LOG_CRIT, "Could not setgid to {$gid}"); exit(1); } } if (!empty($opts["u"])) { $uid = $opts["u"]; if (posix_setuid($uid) === FALSE) { syslog(LOG_CRIT, "Could not setuid to {$uid}"); exit(1); } } $oldpid = null;
<?php /** * - get users + groups from ldap tree * - create mapping * - search/replace all ids in sql dump * * @author Philipp Schüle <*****@*****.**> * * @todo implement * @todo use Zend_Ldap to fetch users/groups * @todo allow to set filename as param * @todo get LDAP bind data from config file * @todo get users/groups to create mapping * @todo preg_replace occurrences in file */ // set include path to find all needed classes set_include_path('./lib' . PATH_SEPARATOR . '/usr/share/tine20' . PATH_SEPARATOR . '/usr/share/tine20/library' . PATH_SEPARATOR . '/etc/tine20' . PATH_SEPARATOR . get_include_path()); require_once 'Zend/Loader/Autoloader.php'; $autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->setFallbackAutoloader(true); $sync = new Sync(); $sync->doSync();
public function testNoteWayTooLong() { $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $content = str_repeat("1", 10000000); // Create too-long note via sync $data = '<data version="9"><items><item libraryID="' . self::$config['libraryID'] . '" itemType="note" ' . 'dateAdded="2009-03-07 04:53:20" ' . 'dateModified="2009-03-07 04:54:09" ' . 'key="AAAAAAAA"><note>' . $content . '</note></item></items></data>'; // // < 4.0.27 // Sync::useZoteroVersion("4.0.26.4"); $response = Sync::upload(self::$sessionID, $updateKey, $data, true); $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true); $this->assertTrue(isset($xml->error)); $this->assertEquals("ERROR_PROCESSING_UPLOAD_DATA", $xml->error["code"]); $this->assertRegExp('/^The note \'.+\' in your library is too long /', (string) $xml->error); $this->assertRegExp('/ copy and paste \'AAAAAAAA\' into /', (string) $xml->error); // // >=4.0.27 // Sync::useZoteroVersion(); $response = Sync::upload(self::$sessionID, $updateKey, $data, true); $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true); $this->assertTrue(isset($xml->error)); $this->assertEquals("NOTE_TOO_LONG", $xml->error["code"]); $this->assertRegExp('/^The note \'.+\' in your library is too long /', (string) $xml->error); $this->assertRegExp('/\\/AAAAAAAA$/', (string) $xml->item); }
public function testComputerProgram() { $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $itemKey = 'AAAAAAAA'; // Create item via sync $data = '<data version="9"><items><item libraryID="' . self::$config['libraryID'] . '" itemType="computerProgram" ' . 'dateAdded="2009-03-07 04:53:20" ' . 'dateModified="2009-03-07 04:54:09" ' . 'key="' . $itemKey . '">' . '<field name="version">1.0</field>' . '</item></items></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); // Get item version via API $response = API::userGet(self::$config['userID'], "items/{$itemKey}?key=" . self::$config['apiKey'] . "&content=json"); $this->assertEquals(200, $response->getStatus()); $xml = API::getItemXML($itemKey); $data = API::parseDataFromAtomEntry($xml); $json = json_decode($data['content'], true); $this->assertEquals('1.0', $json['version']); $json['version'] = '1.1'; $response = API::userPut(self::$config['userID'], "items/{$itemKey}?key=" . self::$config['apiKey'], json_encode($json)); $this->assertEquals(204, $response->getStatus()); $xml = Sync::updated(self::$sessionID); $this->assertEquals('version', (string) $xml->updated[0]->items[0]->item[0]->field[0]['name']); }
/** * Gets called when this course changes. */ private function changed() { Sync::course($this); }
public function testFullTextNoAccess() { API::groupClear(self::$config['ownedPrivateGroupID2']); // Add item to group as user 2 $user2SessionID = Sync::login(['username' => self::$config['username2'], 'password' => self::$config['password2']]); $xml = Sync::updated($user2SessionID); $updateKey = (string) $xml['updateKey']; $key = Zotero_Utilities::randomString(8, 'key', true); $dateAdded = date('Y-m-d H:i:s', time() - 1); $dateModified = date('Y-m-d H:i:s'); $xmlstr = '<data version="9">' . '<items>' . '<item libraryID="' . self::$config['ownedPrivateGroupLibraryID2'] . '" ' . 'itemType="attachment" ' . 'dateAdded="' . $dateAdded . '" ' . 'dateModified="' . $dateModified . '" ' . 'key="' . $key . '"/>' . '</items>' . '</data>'; $response = Sync::upload($user2SessionID, $updateKey, $xmlstr); Sync::waitForUpload($user2SessionID, $response, $this); // Make sure item exists $xml = Sync::updated($user2SessionID, 1); $this->assertEquals(1, $xml->updated[0]->items->count()); $this->assertEquals(1, $xml->updated[0]->items[0]->item->count()); // Try to add full-text content as user 1 $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $content = "This is some full-text content."; $totalChars = 2500; $xmlstr = '<data version="9">' . '<fulltexts>' . '<fulltext libraryID="' . self::$config['ownedPrivateGroupLibraryID2'] . '" ' . 'key="' . $key . '" ' . 'indexedChars="' . strlen($content) . '" ' . 'totalChars="' . $totalChars . '" ' . 'indexedPages="0" ' . 'totalPages="0">' . htmlspecialchars($content) . '</fulltext>' . '</fulltexts>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); // Retrieve it as user 2 $xml = Sync::updated($user2SessionID, 1, false, false, ["ft" => 1]); $this->assertEquals(0, $xml->updated[0]->fulltexts->count()); API::groupClear(self::$config['ownedPrivateGroupID2']); }
public static function check_for_pid($pid) { $OS = Sync::getOS(); if ($OS == 2) { $processes = explode("\n", shell_exec("tasklist.exe")); foreach ($processes as $key => $value) { if (empty($value) != '1' && strpos("Image Name", $value) === 0 || empty($value) != '1' && strpos("===", $value) === 0) { continue; } $matches = false; preg_match("/(.*?)\\s+(\\d+).*\$/", $value, $matches); if (isset($matches[2]) && ($pid = $matches[2])) { return true; } } } else { return file_exists("/proc/" . $pid); } }
public function testCircularRelatedItems() { $parentKey = API::createItem("book", false, null, 'key'); $noteKeys = [API::createNoteItem("Note 1", $parentKey, null, 'key'), API::createNoteItem("Note 2", $parentKey, null, 'key'), API::createNoteItem("Note 3", $parentKey, null, 'key'), API::createNoteItem("Note 4", $parentKey, null, 'key')]; $xml = Sync::updated(self::$sessionID); $updateKey = $xml['updateKey']; $note1XML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[0]}']")); $note2XML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[1]}']")); $note3XML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[2]}']")); $note4XML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[3]}']")); $note1XML['libraryID'] = self::$config['libraryID']; $note2XML['libraryID'] = self::$config['libraryID']; $note3XML['libraryID'] = self::$config['libraryID']; $note4XML['libraryID'] = self::$config['libraryID']; $note1XML->related = implode(' ', [$parentKey, (string) $note2XML['key'], (string) $note3XML['key'], (string) $note4XML['key']]); $note2XML->related = implode(' ', [$parentKey, (string) $note1XML['key'], (string) $note3XML['key'], (string) $note4XML['key']]); $note3XML->related = implode(' ', [$parentKey, (string) $note1XML['key'], (string) $note2XML['key'], (string) $note4XML['key']]); $note4XML->related = implode(' ', [$parentKey, (string) $note1XML['key'], (string) $note2XML['key'], (string) $note3XML['key']]); $xmlstr = '<data version="9">' . '<items>' . $note1XML->asXML() . $note2XML->asXML() . $note3XML->asXML() . $note4XML->asXML() . '</items>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); $xml = Sync::updated(self::$sessionID); $noteXML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[0]}']")); $keys = split(' ', $noteXML->related); $this->assertCount(4, $keys); $this->assertContains($parentKey, $keys); $this->assertContains((string) $noteKeys[1], $keys); $this->assertContains((string) $noteKeys[2], $keys); $this->assertContains((string) $noteKeys[3], $keys); $noteXML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[1]}']")); $keys = split(' ', $noteXML->related); $this->assertCount(4, $keys); $this->assertContains($parentKey, $keys); $this->assertContains((string) $noteKeys[0], $keys); $this->assertContains((string) $noteKeys[2], $keys); $this->assertContains((string) $noteKeys[3], $keys); $noteXML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[2]}']")); $keys = split(' ', $noteXML->related); $this->assertCount(4, $keys); $this->assertContains($parentKey, $keys); $this->assertContains((string) $noteKeys[0], $keys); $this->assertContains((string) $noteKeys[1], $keys); $this->assertContains((string) $noteKeys[3], $keys); $noteXML = array_shift($xml->updated[0]->items->xpath("//item[@key = '{$noteKeys[3]}']")); $keys = split(' ', $noteXML->related); $this->assertCount(4, $keys); $this->assertContains($parentKey, $keys); $this->assertContains((string) $noteKeys[0], $keys); $this->assertContains((string) $noteKeys[1], $keys); $this->assertContains((string) $noteKeys[2], $keys); }
public function testCollectionItemUpdate() { $collectionKey = Sync::createCollection(self::$sessionID, self::$config['libraryID'], "Test", null, $this); $itemKey = Sync::createItem(self::$sessionID, self::$config['libraryID'], "book", null, $this); $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; // Get the item version $itemXML = API::getItemXML($itemKey); $data = API::parseDataFromAtomEntry($itemXML); $json = json_decode($data['content'], true); $itemVersion = $json['itemVersion']; $this->assertNotNull($itemVersion); // Add via sync $collectionXML = $xml->updated[0]->collections[0]->collection[0]; $collectionXML['libraryID'] = self::$config['libraryID']; $collectionXML->addChild("items", $itemKey); $data = '<data version="9"><collections>' . $collectionXML->asXML() . '</collections>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data, true); Sync::waitForUpload(self::$sessionID, $response, $this); // Make sure item was updated $itemXML = API::getItemXML($itemKey); $data = API::parseDataFromAtomEntry($itemXML); $json = json_decode($data['content'], true); $this->assertGreaterThan($itemVersion, $json['itemVersion']); $itemVersion = $json['itemVersion']; $this->assertCount(1, $json['collections']); $this->assertContains($collectionKey, $json['collections']); // Remove via sync $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; $collectionXML = $xml->updated[0]->collections[0]->collection[0]; $collectionXML['libraryID'] = self::$config['libraryID']; unset($collectionXML->items); $data = '<data version="9"><collections>' . $collectionXML->asXML() . '</collections>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data, true); Sync::waitForUpload(self::$sessionID, $response, $this); // Make sure item was removed $itemXML = API::getItemXML($itemKey); $data = API::parseDataFromAtomEntry($itemXML); $json = json_decode($data['content'], true); $this->assertGreaterThan($itemVersion, $json['itemVersion']); $this->assertCount(0, $json['collections']); }
public function testSyncUploadUnchanged() { $data1 = API::createItem("audioRecording", array("title" => "Test", "relations" => array('owl:sameAs' => 'http://zotero.org/groups/1/items/AAAAAAAA')), null, 'data'); // dc:relation already exists, so item shouldn't change $data2 = API::createItem("interview", array("relations" => array('dc:relation' => 'http://zotero.org/users/' . self::$config['userID'] . '/items/' . $data1['key'])), null, 'data'); // Upload unchanged via sync $xml = Sync::updated(self::$sessionID); $updateKey = $xml['updateKey']; $lastSyncTimestamp = $xml['timestamp']; $itemXML1 = array_shift($xml->updated[0]->items[0]->xpath("item[@key='{$data1['key']}']")); $itemXML2 = array_shift($xml->updated[0]->items[0]->xpath("item[@key='{$data2['key']}']")); $itemXML1['libraryID'] = self::$config['libraryID']; $itemXML2['libraryID'] = self::$config['libraryID']; $xmlstr = '<data version="9">' . '<items>' . $itemXML1->asXML() . $itemXML2->asXML() . '</items>' . '</data>'; $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr); Sync::waitForUpload(self::$sessionID, $response, $this); // Check via API to make sure they're the same $response = API::userGet(self::$config['userID'], "items?key=" . self::$config['apiKey'] . "&format=versions"); $json = API::getJSONFromResponse($response); $this->assertEquals($data1['version'], $json[$data1['key']]); $this->assertEquals($data2['version'], $json[$data2['key']]); }
/** * Handles the Sync command * Performs the synchronization of messages * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Contains all requested folders (containers) $sc = new SyncCollections(); $status = SYNC_STATUS_SUCCESS; $wbxmlproblem = false; $emptysync = false; // check if the hierarchySync was fully completed if (USE_PARTIAL_FOLDERSYNC) { if (self::$deviceManager->GetFolderSyncComplete() === false) { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed"); self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true); $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; } else { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete"); } } // Start Synchronize if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { // AS 1.0 sends version information in WBXML if (self::$decoder->getElementStartTag(SYNC_VERSION)) { $sync_version = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version)); if (!self::$decoder->getElementEndTag()) { return false; } } // Synching specified folders // Android still sends heartbeat sync even if all syncfolders are disabled. // Check if Folders tag is empty (<Folders/>) and only sync if there are // some folders in the request. See ZP-172 $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS); if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) { while (self::$decoder->getElementStartTag(SYNC_FOLDER)) { $actiondata = array(); $actiondata["requested"] = true; $actiondata["clientids"] = array(); $actiondata["modifyids"] = array(); $actiondata["removeids"] = array(); $actiondata["fetchids"] = array(); $actiondata["statusids"] = array(); // read class, synckey and folderid without SyncParameters Object for now $class = $synckey = $folderid = false; //for AS versions < 2.5 if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $class = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class)); if (!self::$decoder->getElementEndTag()) { return false; } } // SyncKey if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { $synckey = "0"; if (($synckey = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { return false; } } } else { return false; } // FolderId if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { $folderid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() if (!$folderid && $class) { $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class); } // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update try { $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid); // TODO remove resync of folders for < Z-Push 2 beta4 users // this forces a resync of all states previous to Z-Push 2 beta4 if (!$spa instanceof SyncParameters) { throw new StateInvalidException("Saved state are not of type SyncParameters"); } // new/resync requested if ($synckey == "0") { $spa->RemoveSyncKey(); } else { if ($synckey !== false) { $spa->SetSyncKey($synckey); } } } catch (StateInvalidException $stie) { $spa = new SyncParameters(); $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("State invalid - Resync folder", true); self::$deviceManager->ForceFolderResync($folderid); } // update folderid.. this might be a new object $spa->SetFolderId($folderid); if ($class !== false) { $spa->SetContentClass($class); } // Get class for as versions >= 12.0 if (!$spa->HasContentClass()) { try { $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId())); } catch (NoHierarchyCacheAvailableException $nhca) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$deviceManager->ForceFullResync(); } } // done basic SPA initialization/loading -> add to SyncCollection $sc->AddCollection($spa); $sc->AddParameter($spa, "requested", true); if ($spa->HasContentClass()) { self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true); } else { ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache."); } // SUPPORTED properties if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) { // ZP-481: LG phones send an empty supported tag, so only read the contents if available here // if <Supported/> is received, it's as no supported fields would have been sent at all. // unsure if this is the correct approach, or if in this case some default list should be used if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) { $supfields = array(); while (1) { $el = self::$decoder->getElement(); if ($el[EN_TYPE] == EN_TYPE_ENDTAG) { break; } else { $supfields[] = $el[EN_TAG]; } } self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); } } // Deletes as moves can be an empty tag as well as have value if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { $spa->SetDeletesAsMoves(true); if (($dam = self::$decoder->getElementContent()) !== false) { $spa->SetDeletesAsMoves((bool) $dam); if (!self::$decoder->getElementEndTag()) { return false; } } } // Get changes can be an empty tag as well as have value // code block partly contributed by dw2412 if (self::$decoder->getElementStartTag(SYNC_GETCHANGES)) { $sc->AddParameter($spa, "getchanges", true); if (($gc = self::$decoder->getElementContent()) !== false) { $sc->AddParameter($spa, "getchanges", $gc); if (!self::$decoder->getElementEndTag()) { return false; } } } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $ws = self::$decoder->getElementContent(); // normalize windowsize - see ZP-477 if ($ws == 0 || $ws > 512) { $ws = 512; } $spa->SetWindowSize($ws); // also announce the currently requested window size to the DeviceManager self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize()); if (!self::$decoder->getElementEndTag()) { return false; } } // conversation mode requested if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if (($conversationmode = self::$decoder->getElementContent()) !== false) { $spa->SetConversationMode((bool) $conversationmode); if (!self::$decoder->getElementEndTag()) { return false; } } } // Do not truncate by default $spa->SetTruncation(SYNC_TRUNCATION_ALL); // use default conflict handling if not specified by the mobile $spa->SetConflict(SYNC_CONFLICT_DEFAULT); while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) { $firstOption = true; while (1) { // foldertype definition if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype)); // switch the foldertype for the next options $spa->UseCPO($foldertype); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); if (!self::$decoder->getElementEndTag()) { return false; } } else { if ($firstOption) { $spa->UseCPO(); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); } } $firstOption = false; if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { $spa->SetFilterType(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) { $spa->SetTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { $spa->SetRTFTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { $spa->SetMimeSupport(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { $spa->SetMimeTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) { $spa->SetConflict(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { $bptype = self::$decoder->getElementContent(); $spa->BodyPreference($bptype); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } } $e = self::$decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { self::$decoder->getElementEndTag(); break; } } } // limit items to be synchronized to the mobiles if configured if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX)); $spa->SetFilterType(SYNC_FILTERTIME_MAX); } // Check if the hierarchycache is available. If not, trigger a HierarchySync if (self::$deviceManager->IsHierarchySyncRequired()) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device"); } if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) { // We can not proceed here as the content class is unknown if ($status != SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem."); $wbxmlproblem = true; break; } $performaction = true; // unset the importer $this->importer = false; $nchanges = 0; while (1) { // ADD, MODIFY, REMOVE or FETCH $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { self::$decoder->ungetElement($element); break; } if ($status == SYNC_STATUS_SUCCESS) { $nchanges++; } // Foldertype sent when synching SMS if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype)); if (!self::$decoder->getElementEndTag()) { return false; } } else { $foldertype = false; } $serverid = false; if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { if (($serverid = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { // end serverid return false; } } } if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { $clientid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // end clientid return false; } } else { $clientid = false; } // Get the SyncMessage if sent if (self::$decoder->getElementStartTag(SYNC_DATA)) { $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()); $message->Decode(self::$decoder); // set Ghosted fields $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId())); if (!self::$decoder->getElementEndTag()) { // end applicationdata return false; } } else { $message = false; } switch ($element[EN_TAG]) { case SYNC_FETCH: array_push($actiondata["fetchids"], $serverid); break; default: // get the importer if ($this->importer == false) { $status = $this->getImporter($sc, $spa, $actiondata); } if ($status == SYNC_STATUS_SUCCESS) { $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges); } else { ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem."); } break; } if ($actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges)); } else { self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges)); } if (!self::$decoder->getElementEndTag()) { // end add/change/delete/move return false; } } if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { ZLog::Write(LOGLEVEL_INFO, sprintf("Sync->Handle(): Processed %d incoming changes", $nchanges)); if (!$actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true); } try { // Save the updated state, which is used for the exporter later $sc->AddParameter($spa, "state", $this->importer->GetState()); } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { // end PERFORM return false; } } // save the failsave state if (!empty($actiondata["statusids"])) { unset($actiondata["failstate"]); $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state"); self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata); } // save actiondata $sc->AddParameter($spa, "actiondata", $actiondata); if (!self::$decoder->getElementEndTag()) { // end collection return false; } // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) { $sc->AddParameter($spa, "getchanges", true); } } // END FOLDER if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end collections return false; } } // end FOLDERS if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) { $hbinterval = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_HEARTBEATINTERVAL return false; } } if (self::$decoder->getElementStartTag(SYNC_WAIT)) { $wait = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_WAIT return false; } // internally the heartbeat interval and the wait time are the same // heartbeat is in seconds, wait in minutes $hbinterval = $wait * 60; } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $sc->SetGlobalWindowSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { // SYNC_WINDOWSIZE return false; } } if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) { $partial = true; } else { $partial = false; } if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end sync return false; } } else { $emptysync = true; } // END SYNCHRONIZE // check heartbeat/wait time if (isset($hbinterval)) { if ($hbinterval < 60 || $hbinterval > 3540) { $status = SYNC_STATUS_INVALIDWAITORHBVALUE; ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval)); } } // Partial & Empty Syncs need saved data to proceed with synchronization if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders.")); // Load all collections - do not overwrite existing (received!), load states and check permissions try { $sc->LoadAllCollections(false, true, true); } catch (StateNotFoundException $snfex) { $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("StateNotFoundException", true); } catch (StatusException $stex) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } // update a few values foreach ($sc as $folderid => $spa) { // manually set getchanges parameter for this collection $sc->AddParameter($spa, "getchanges", true); // set new global windowsize without marking the SPA as changed if ($sc->GetGlobalWindowSize()) { $spa->SetWindowSize($sc->GetGlobalWindowSize(), false); } // announce WindowSize to DeviceManager self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); } if (!$sc->HasCollections()) { $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; } } // HEARTBEAT & Empty sync if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) { $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30; if (isset($hbinterval)) { $sc->SetLifetime($hbinterval); } // states are lazy loaded - we have to make sure that they are there! $loadstatus = SYNC_STATUS_SUCCESS; foreach ($sc as $folderid => $spa) { // some androids do heartbeat on the OUTBOX folder, with weird results - ZP-362 // we do not load the state so we will never get relevant changes on the OUTBOX folder if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed")); continue; } $fad = array(); // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY if ($loadstatus == SYNC_STATUS_SUCCESS) { $loadstatus = $this->loadStates($sc, $spa, $fad); } } if ($loadstatus == SYNC_STATUS_SUCCESS) { $foundchanges = false; try { // always check for changes ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode")); $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval); } catch (StatusException $stex) { if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } } // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) { // if there were changes to the SPA or CPOs we need to save this before we terminate // only save if the state was not modified by some other request, if so, return state invalid status foreach ($sc as $folderid => $spa) { if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $sc->SaveCollection($spa); } } if ($status == SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection."); self::$specialHeaders = array(); self::$specialHeaders[] = "Connection: close"; return true; } } if ($foundchanges) { foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) { // check if there were other sync requests for a folder during the heartbeat $spa = $sc->GetCollection($folderid); if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid)); $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid)); } } } } } ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output")); // Start the output self::$encoder->startWBXML(); self::$encoder->startTag(SYNC_SYNCHRONIZE); // global status // SYNC_COMMONSTATUS_* start with values from 101 if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) { self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); } else { self::$encoder->startTag(SYNC_FOLDERS); foreach ($sc as $folderid => $spa) { // get actiondata $actiondata = $sc->GetParameter($spa, "actiondata"); if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection.")); continue; } if (!$sc->GetParameter($spa, "requested")) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId())); } // initialize exporter to get changecount $changecount = false; if (isset($exporter)) { unset($exporter); } // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) { //make sure the states are loaded $status = $this->loadStates($sc, $spa, $actiondata); if ($status == SYNC_STATUS_SUCCESS) { try { // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } // Use the state from the importer, as changes may have already happened $exporter = self::$backend->GetExporter($spa->GetFolderId()); if ($exporter === false) { throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } } catch (StatusException $stex) { $status = $stex->getCode(); } try { // Stream the messages directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass())); if ($exporter !== false) { $exporter->Config($sc->GetParameter($spa, "state")); $exporter->ConfigContentParameters($spa->GetCPO()); $exporter->InitializeExporter($streamimporter); $changecount = $exporter->GetChangeCount(); } } catch (StatusException $stex) { if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) { $status = SYNC_STATUS_INVALIDSYNCKEY; } else { $status = $stex->getCode(); } } if (!$spa->HasSyncKey()) { self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true); // update folder status as initialized $spa->SetFolderSyncTotal($changecount); $spa->SetFolderSyncRemaining($changecount); if ($changecount > 0) { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED); } } else { if ($status != SYNC_STATUS_SUCCESS) { self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } } } } if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output."); continue; } // Get a new sync key to output to the client if any changes have been send or will are available if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || $changecount > 0 || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) { $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); } self::$encoder->startTag(SYNC_FOLDER); if ($spa->HasContentClass()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass())); // AS 12.0 devices require content class if (Request::GetProtocolVersion() < 12.1) { self::$encoder->startTag(SYNC_FOLDERTYPE); self::$encoder->content($spa->GetContentClass()); self::$encoder->endTag(); } } self::$encoder->startTag(SYNC_SYNCKEY); if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) { self::$encoder->content($spa->GetNewSyncKey()); } else { self::$encoder->content($spa->GetSyncKey()); } self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERID); self::$encoder->content($spa->GetFolderId()); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); // announce failing status to the process loop detection if ($status !== SYNC_STATUS_SUCCESS) { self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); } // Output IDs and status for incoming items & requests if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) { self::$encoder->startTag(SYNC_REPLIES); // output result of all new incoming items foreach ($actiondata["clientids"] as $clientid => $serverid) { self::$encoder->startTag(SYNC_ADD); self::$encoder->startTag(SYNC_CLIENTENTRYID); self::$encoder->content($clientid); self::$encoder->endTag(); if ($serverid) { self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); } self::$encoder->startTag(SYNC_STATUS); self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR); self::$encoder->endTag(); self::$encoder->endTag(); } // loop through modify operations which were not a success, send status foreach ($actiondata["modifyids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_MODIFY); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } // loop through remove operations which were not a success, send status foreach ($actiondata["removeids"] as $serverid) { if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_REMOVE); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($actiondata["statusids"][$serverid]); self::$encoder->endTag(); self::$encoder->endTag(); } } if (!empty($actiondata["fetchids"])) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true); } foreach ($actiondata["fetchids"] as $id) { $data = false; try { $fetchstatus = SYNC_STATUS_SUCCESS; // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND); } $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO()); // check if the message is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id)); $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; } } catch (StatusException $stex) { $fetchstatus = $stex->getCode(); } self::$encoder->startTag(SYNC_FETCH); self::$encoder->startTag(SYNC_SERVERENTRYID); self::$encoder->content($id); self::$encoder->endTag(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($fetchstatus); self::$encoder->endTag(); if ($data !== false && $status == SYNC_STATUS_SUCCESS) { self::$encoder->startTag(SYNC_DATA); $data->Encode(self::$encoder); self::$encoder->endTag(); } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id)); } self::$encoder->endTag(); } self::$encoder->endTag(); } if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); if ($changecount > $windowSize) { self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); } } // Stream outgoing changes if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) { self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount)); // Output message changes per folder self::$encoder->startTag(SYNC_PERFORM); $n = 0; while (1) { try { $progress = $exporter->Synchronize(); if (!is_array($progress)) { break; } $n++; } catch (SyncObjectBrokenException $mbe) { $brokenSO = $mbe->GetSyncObject(); if (!$brokenSO) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend.")); } else { if (!isset($brokenSO->id)) { $brokenSO->id = "Unknown ID"; ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend.")); } self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); } } catch (StatusException $stex) { $status = $stex->getCode(); // during export we found out that the states should be thrown away (ZP-623) if ($status == SYNC_STATUS_INVALIDSYNCKEY) { self::$deviceManager->ForceFolderResync($spa->GetFolderId()); break; } } if ($n >= $windowSize) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); break; } } // $progress is not an array when exporting the last message // so we get the number to display from the streamimporter if (isset($streamimporter)) { $n = $streamimporter->GetImportedMessages(); } self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), true); // update folder status, if there is something set if ($spa->GetFolderSyncRemaining() && $changecount > 0) { $spa->SetFolderSyncRemaining($changecount); } // changecount is initialized with 'false', so 0 means no changes! if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED); } else { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INPROGRESS); } } self::$encoder->endTag(); // Save the sync state for the next time if ($spa->HasNewSyncKey()) { self::$topCollector->AnnounceInformation("Saving state"); try { if (isset($exporter) && $exporter) { $state = $exporter->GetState(); } else { if ($sc->GetParameter($spa, "state") !== null) { $state = $sc->GetParameter($spa, "state"); } else { if (!$spa->HasSyncKey()) { $state = ""; } } } } catch (StatusException $stex) { $status = $stex->getCode(); } if (isset($state) && $status == SYNC_STATUS_SUCCESS) { self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId()); } else { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); } } // save SyncParameters if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) { $sc->SaveCollection($spa); } // reset status for the next folder $status = SYNC_STATUS_SUCCESS; } // END foreach collection self::$encoder->endTag(); //SYNC_FOLDERS } self::$encoder->endTag(); //SYNC_SYNCHRONIZE return true; }
public function testDownloadCache() { $keys = []; $keys[] = API::createItem("book", false, false, 'key'); $keys[] = API::createItem("journalArticle", false, false, 'key'); $keys[] = API::createItem("newspaperArticle", false, false, 'key'); $keys[] = API::createItem("magazineArticle", false, false, 'key'); $keys[] = API::createItem("bookSection", false, false, 'key'); $keys[] = API::createItem("audioRecording", false, false, 'key'); $xml1 = Sync::updated(self::$sessionID); $xml2 = Sync::updated(self::$sessionID); $this->assertEquals(preg_replace('/timestamp="\\d+"/', 'timestamp="--"', $xml1->asXML()), preg_replace('/timestamp="\\d+"/', 'timestamp="--"', $xml2->asXML())); }
public static function waitForUpload($sessionID, $response, $context, $allowError = false) { $xml = Sync::getXMLFromResponse($response); if (isset($xml->uploaded) || isset($xml->error) && $allowError) { return $xml; } $context->assertTrue(isset($xml->queued)); $max = 5; do { $wait = (int) $xml->queued['wait']; sleep($wait / 1000); $response = Sync::uploadStatus($sessionID, $allowError); $xml = Sync::getXMLFromResponse($response); $max--; } while (isset($xml->queued) && $max > 0); if (!$max) { $context->fail("Upload did not finish after {$max} attempts"); } if (!$allowError) { $context->assertTrue(isset($xml->uploaded)); } return $xml; }
/** * Handles the Sync command * Performs the synchronization of messages * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Contains all requested folders (containers) $sc = new SyncCollections(); $status = SYNC_STATUS_SUCCESS; $wbxmlproblem = false; $emptysync = false; $this->singleFolder = true; $this->multiFolderInfo = array(); $this->globallyExportedItems = 0; // check if the hierarchySync was fully completed if (USE_PARTIAL_FOLDERSYNC) { if (self::$deviceManager->GetFolderSyncComplete() === false) { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed"); self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true); $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; } else { ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete"); } } // Start Synchronize if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { // AS 1.0 sends version information in WBXML if (self::$decoder->getElementStartTag(SYNC_VERSION)) { $sync_version = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version)); if (!self::$decoder->getElementEndTag()) { return false; } } // Synching specified folders // Android still sends heartbeat sync even if all syncfolders are disabled. // Check if Folders tag is empty (<Folders/>) and only sync if there are // some folders in the request. See ZP-172 $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS); if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) { while (self::$decoder->getElementStartTag(SYNC_FOLDER)) { $actiondata = array(); $actiondata["requested"] = true; $actiondata["clientids"] = array(); $actiondata["modifyids"] = array(); $actiondata["removeids"] = array(); $actiondata["fetchids"] = array(); $actiondata["statusids"] = array(); // read class, synckey and folderid without SyncParameters Object for now $class = $synckey = $folderid = false; // if there are already collections in SyncCollections, this is min. the second folder if ($sc->HasCollections()) { $this->singleFolder = false; } //for AS versions < 2.5 if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $class = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class)); if (!self::$decoder->getElementEndTag()) { return false; } } // SyncKey if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { $synckey = "0"; if (($synckey = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { return false; } } } else { return false; } // FolderId if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { $folderid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() if (!$folderid && $class) { $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class); } // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update try { $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid); // TODO remove resync of folders for < Z-Push 2 beta4 users // this forces a resync of all states previous to Z-Push 2 beta4 if (!$spa instanceof SyncParameters) { throw new StateInvalidException("Saved state are not of type SyncParameters"); } // new/resync requested if ($synckey == "0") { $spa->RemoveSyncKey(); $spa->DelFolderStat(); $spa->SetMoveState(false); } else { if ($synckey !== false) { if ($synckey !== $spa->GetSyncKey() && $synckey !== $spa->GetNewSyncKey() || !!$spa->GetMoveState()) { ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder or there is a move state, removing folderstat to force Exporter setup"); $spa->DelFolderStat(); } $spa->SetSyncKey($synckey); } } } catch (StateInvalidException $stie) { $spa = new SyncParameters(); $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("State invalid - Resync folder", $this->singleFolder); self::$deviceManager->ForceFolderResync($folderid); $this->saveMultiFolderInfo("exception", "StateInvalidException"); } // update folderid.. this might be a new object $spa->SetFolderId($folderid); $spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid)); if ($class !== false) { $spa->SetContentClass($class); } // Get class for as versions >= 12.0 if (!$spa->HasContentClass()) { try { $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId())); } catch (NoHierarchyCacheAvailableException $nhca) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$deviceManager->ForceFullResync(); } } // done basic SPA initialization/loading -> add to SyncCollection $sc->AddCollection($spa); $sc->AddParameter($spa, "requested", true); if ($spa->HasContentClass()) { self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), $this->singleFolder); } else { ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache."); } // SUPPORTED properties if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) { // ZP-481: LG phones send an empty supported tag, so only read the contents if available here // if <Supported/> is received, it's as no supported fields would have been sent at all. // unsure if this is the correct approach, or if in this case some default list should be used if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) { $supfields = array(); WBXMLDecoder::ResetInWhile("syncSupported"); while (WBXMLDecoder::InWhile("syncSupported")) { $el = self::$decoder->getElement(); if ($el[EN_TYPE] == EN_TYPE_ENDTAG) { break; } else { $supfields[] = $el[EN_TAG]; } } self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); } } // Deletes as moves can be an empty tag as well as have value if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { $spa->SetDeletesAsMoves(true); if (($dam = self::$decoder->getElementContent()) !== false) { $spa->SetDeletesAsMoves((bool) $dam); if (!self::$decoder->getElementEndTag()) { return false; } } } // Get changes can be an empty tag as well as have value // code block partly contributed by dw2412 if (self::$decoder->getElementStartTag(SYNC_GETCHANGES)) { $sc->AddParameter($spa, "getchanges", true); if (($gc = self::$decoder->getElementContent()) !== false) { $sc->AddParameter($spa, "getchanges", $gc); if (!self::$decoder->getElementEndTag()) { return false; } } } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $ws = self::$decoder->getElementContent(); // normalize windowsize - see ZP-477 if ($ws == 0 || $ws > WINDOW_SIZE_MAX) { $ws = WINDOW_SIZE_MAX; } $spa->SetWindowSize($ws); // also announce the currently requested window size to the DeviceManager self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize()); if (!self::$decoder->getElementEndTag()) { return false; } } // conversation mode requested if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if (($conversationmode = self::$decoder->getElementContent()) !== false) { $spa->SetConversationMode((bool) $conversationmode); if (!self::$decoder->getElementEndTag()) { return false; } } } // Do not truncate by default $spa->SetTruncation(SYNC_TRUNCATION_ALL); // use default conflict handling if not specified by the mobile $spa->SetConflict(SYNC_CONFLICT_DEFAULT); // save the current filtertype because it might have been changed on the mobile $currentFilterType = $spa->GetFilterType(); while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) { $firstOption = true; WBXMLDecoder::ResetInWhile("syncOptions"); while (WBXMLDecoder::InWhile("syncOptions")) { // foldertype definition if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype)); // switch the foldertype for the next options $spa->UseCPO($foldertype); // save the current filtertype because it might have been changed on the mobile $currentFilterType = $spa->GetFilterType(); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); if (!self::$decoder->getElementEndTag()) { return false; } } else { if ($firstOption) { $spa->UseCPO(); // save the current filtertype because it might have been changed on the mobile $currentFilterType = $spa->GetFilterType(); // set to synchronize all changes. The mobile could overwrite this value $spa->SetFilterType(SYNC_FILTERTYPE_ALL); } } $firstOption = false; if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { $spa->SetFilterType(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) { $spa->SetTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { $spa->SetRTFTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { $spa->SetMimeSupport(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { $spa->SetMimeTruncation(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) { $spa->SetConflict(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { $bptype = self::$decoder->getElementContent(); $spa->BodyPreference($bptype); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } } $e = self::$decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { self::$decoder->getElementEndTag(); break; } } } // limit items to be synchronized to the mobiles if configured if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX)); $spa->SetFilterType(SYNC_FILTERTIME_MAX); } // unset filtertype for KOE GAB folder if (KOE_CAPABILITY_GAB && self::$deviceManager->IsKoe() && $spa->GetBackendFolderId() == self::$deviceManager->GetKoeGabBackendFolderId()) { $spa->SetFilterType(SYNC_FILTERTYPE_ALL); ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): KOE GAB folder - setting filter type to unlimited"); } if ($currentFilterType != $spa->GetFilterType()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): filter type has changed (old: '%s', new: '%s'), removing folderstat to force Exporter setup", $currentFilterType, $spa->GetFilterType())); $spa->DelFolderStat(); } // Check if the hierarchycache is available. If not, trigger a HierarchySync if (self::$deviceManager->IsHierarchySyncRequired()) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device"); } if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) { // We can not proceed here as the content class is unknown if ($status != SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem."); $wbxmlproblem = true; break; } $performaction = true; // unset the importer $this->importer = false; $nchanges = 0; WBXMLDecoder::ResetInWhile("syncActions"); while (WBXMLDecoder::InWhile("syncActions")) { // ADD, MODIFY, REMOVE or FETCH $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { self::$decoder->ungetElement($element); break; } if ($status == SYNC_STATUS_SUCCESS) { $nchanges++; } // Foldertype sent when synching SMS if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $foldertype = self::$decoder->getElementContent(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype)); if (!self::$decoder->getElementEndTag()) { return false; } } else { $foldertype = false; } $serverid = false; if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { if (($serverid = self::$decoder->getElementContent()) !== false) { if (!self::$decoder->getElementEndTag()) { // end serverid return false; } } } if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { $clientid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // end clientid return false; } } else { $clientid = false; } // Get the SyncMessage if sent if (($el = self::$decoder->getElementStartTag(SYNC_DATA)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) { $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()); // KOE ZO-42: OL sends Notes as Appointments if ($spa->GetContentClass() == "Notes" && KOE_CAPABILITY_NOTES && self::$deviceManager->IsKoe()) { ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): KOE sends Notes as Appointments, read as SyncAppointment and convert it into a SyncNote object."); $message = new SyncAppointment(); $message->Decode(self::$decoder); $note = new SyncNote(); if (isset($message->asbody)) { $note->asbody = $message->asbody; } if (isset($message->categories)) { $note->categories = $message->categories; } if (isset($message->subject)) { $note->subject = $message->subject; } if (isset($message->dtstamp)) { $note->lastmodified = $message->dtstamp; } // set SyncNote->Color from a color category $note->SetColorFromCategory(); $message = $note; } else { $message->Decode(self::$decoder); // set Ghosted fields $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId())); } if (!self::$decoder->getElementEndTag()) { // end applicationdata return false; } } else { $message = false; } switch ($element[EN_TAG]) { case SYNC_FETCH: array_push($actiondata["fetchids"], $serverid); break; default: // get the importer if ($this->importer == false) { $status = $this->getImporter($sc, $spa, $actiondata); } if ($status == SYNC_STATUS_SUCCESS) { $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges); } else { ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem."); } break; } if ($actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges)); } else { self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges)); } if (!self::$decoder->getElementEndTag()) { // end add/change/delete/move return false; } } if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges)); if (!$actiondata["fetchids"]) { self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), $this->singleFolder); $this->saveMultiFolderInfo("incoming", $nchanges); } try { // Save the updated state, which is used for the exporter later $sc->AddParameter($spa, "state", $this->importer->GetState()); } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { // end PERFORM return false; } } // save the failsave state if (!empty($actiondata["statusids"])) { unset($actiondata["failstate"]); $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state"); self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata); } // save actiondata $sc->AddParameter($spa, "actiondata", $actiondata); if (!self::$decoder->getElementEndTag()) { // end collection return false; } // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) { $sc->AddParameter($spa, "getchanges", true); } } // END FOLDER if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end collections return false; } } // end FOLDERS if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) { $hbinterval = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_HEARTBEATINTERVAL return false; } } if (self::$decoder->getElementStartTag(SYNC_WAIT)) { $wait = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { // SYNC_WAIT return false; } // internally the heartbeat interval and the wait time are the same // heartbeat is in seconds, wait in minutes $hbinterval = $wait * 60; } if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { $sc->SetGlobalWindowSize(self::$decoder->getElementContent()); ZLog::Write(LOGLEVEL_DEBUG, "Sync(): Global WindowSize requested: " . $sc->GetGlobalWindowSize()); if (!self::$decoder->getElementEndTag()) { // SYNC_WINDOWSIZE return false; } } if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) { $partial = true; } else { $partial = false; } if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end sync return false; } } else { $emptysync = true; } // END SYNCHRONIZE // check heartbeat/wait time if (isset($hbinterval)) { if ($hbinterval < 60 || $hbinterval > 3540) { $status = SYNC_STATUS_INVALIDWAITORHBVALUE; ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval)); } } // Partial & Empty Syncs need saved data to proceed with synchronization if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders.")); // Load all collections - do not overwrite existing (received!), load states, check permissions and only load confirmed states! try { $sc->LoadAllCollections(false, true, true, true, true); } catch (StateInvalidException $siex) { $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder); $this->saveMultiFolderInfo("exeption", "StateNotFoundException"); } catch (StatusException $stex) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); $this->saveMultiFolderInfo("exeption", "StatusException"); } // update a few values foreach ($sc as $folderid => $spa) { // manually set getchanges parameter for this collection if it is synchronized if ($spa->HasSyncKey()) { $sc->AddParameter($spa, "getchanges", true); // announce WindowSize to DeviceManager self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); } } if (!$sc->HasCollections()) { $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; } } else { if (isset($hbinterval)) { // load the hierarchy data - there are no permissions to verify so we just set it to false if (!$sc->LoadCollection(false, true, false)) { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); $this->saveMultiFolderInfo("exeption", "StatusException"); } } } // HEARTBEAT & Empty sync if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) { $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30; if (isset($hbinterval)) { $sc->SetLifetime($hbinterval); } // states are lazy loaded - we have to make sure that they are there! $loadstatus = SYNC_STATUS_SUCCESS; foreach ($sc as $folderid => $spa) { // some androids do heartbeat on the OUTBOX folder, with weird results - ZP-362 // we do not load the state so we will never get relevant changes on the OUTBOX folder if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed")); continue; } $fad = array(); // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY if ($loadstatus == SYNC_STATUS_SUCCESS) { $loadstatus = $this->loadStates($sc, $spa, $fad); } } if ($loadstatus == SYNC_STATUS_SUCCESS) { $foundchanges = false; try { // always check for changes ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode")); $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval); } catch (StatusException $stex) { if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); $this->saveMultiFolderInfo("exeption", "StatusException"); } } // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) { // if there were changes to the SPA or CPOs we need to save this before we terminate // only save if the state was not modified by some other request, if so, return state invalid status foreach ($sc as $folderid => $spa) { if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { $sc->SaveCollection($spa); } } if ($status == SYNC_STATUS_SUCCESS) { ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection."); self::$specialHeaders = array(); self::$specialHeaders[] = "Connection: close"; return true; } } if ($foundchanges) { foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) { // check if there were other sync requests for a folder during the heartbeat $spa = $sc->GetCollection($folderid); if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid)); $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid)); } } } } } // Start the output ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Start Output"); // global status // SYNC_COMMONSTATUS_* start with values from 101 if ($status != SYNC_COMMONSTATUS_SUCCESS && ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED || $status > 100)) { $this->sendStartTags(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->endTag(); // SYNC_SYNCHRONIZE return true; } // Loop through requested folders foreach ($sc as $folderid => $spa) { // get actiondata $actiondata = $sc->GetParameter($spa, "actiondata"); if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) { ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection.")); continue; } if (!$sc->GetParameter($spa, "requested")) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId())); } // initialize exporter to get changecount $changecount = false; $exporter = false; $streamimporter = false; $newFolderStat = false; $setupExporter = true; // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) { // no need to run the exporter if the globalwindowsize is already full if ($sc->GetGlobalWindowSize() == $this->globallyExportedItems) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as GlobalWindowSize is full.", $spa->GetFolderId())); $setupExporter = false; } // if the maximum request timeout is reached, stop processing other collections if (Request::IsRequestTimeoutReached()) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as request timeout reached, omitting output for collection.", $spa->GetFolderId())); $setupExporter = false; } // compare the folder statistics if the backend supports this if ($setupExporter && self::$backend->HasFolderStats()) { // check if the folder stats changed -> if not, don't setup the exporter, there are no changes! $newFolderStat = self::$backend->GetFolderStat(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId()); if ($newFolderStat !== false && !$spa->IsExporterRunRequired($newFolderStat, true)) { $changecount = 0; $setupExporter = false; } } // Do a full Exporter setup if we can't avoid it if ($setupExporter) { //make sure the states are loaded $status = $this->loadStates($sc, $spa, $actiondata); if ($status == SYNC_STATUS_SUCCESS) { try { // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } // Use the state from the importer, as changes may have already happened $exporter = self::$backend->GetExporter($spa->GetBackendFolderId()); if ($exporter === false) { throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } } catch (StatusException $stex) { $status = $stex->getCode(); } try { // Stream the messages directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass())); if ($exporter !== false) { $exporter->SetMoveStates($spa->GetMoveState()); $exporter->Config($sc->GetParameter($spa, "state")); $exporter->ConfigContentParameters($spa->GetCPO()); $exporter->InitializeExporter($streamimporter); $changecount = $exporter->GetChangeCount(); } } catch (StatusException $stex) { if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) { $status = SYNC_STATUS_INVALIDSYNCKEY; } else { $status = $stex->getCode(); } } if (!$spa->HasSyncKey()) { self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), $this->singleFolder); $this->saveMultiFolderInfo("queued", $changecount); // update folder status as initialized $spa->SetFolderSyncTotal($changecount); $spa->SetFolderSyncRemaining($changecount); if ($changecount > 0) { self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED); } } else { if ($status != SYNC_STATUS_SUCCESS) { self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); $this->saveMultiFolderInfo("exception", "StatusException"); } } } } } // Get a new sync key to output to the client if any changes have been send by the mobile or a new synckey is to be sent if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) { $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); } else { // when reaching the global limit for changes of all collections, stop processing other collections (ZP-697) if ($sc->GetGlobalWindowSize() <= $this->globallyExportedItems) { ZLog::Write(LOGLEVEL_DEBUG, "Global WindowSize for amount of exported changes reached, omitting output for collection."); continue; } // get a new synckey if there are changes are we did not reach the limit yet if ($changecount > 0) { $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); } } // Fir AS 14.0+ omit output for folder, if there were no incoming or outgoing changes and no Fetch if (Request::GetProtocolVersion() >= 14.0 && !$spa->HasNewSyncKey() && $changecount == 0 && empty($actiondata["fetchids"]) && $status == SYNC_STATUS_SUCCESS && ($newFolderStat === false || !$spa->IsExporterRunRequired($newFolderStat))) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: No changes found for %s folder id '%s'. Omitting output.", $spa->GetContentClass(), $spa->GetFolderId())); continue; } // if there are no other responses sent, we should end with a global status if ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED && $this->startTagsSent === false) { $this->sendStartTags(); self::$encoder->startTag(SYNC_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->endTag(); // SYNC_SYNCHRONIZE return true; } // there is something to send here, sync folder to output $this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat); // reset status for the next folder $status = SYNC_STATUS_SUCCESS; } // END foreach collection //SYNC_FOLDERS - only if the starttag was sent if ($this->startFolderTagSent) { self::$encoder->endTag(); } //SYNC_SYNCHRONIZE - only if the starttag was sent if ($this->startTagsSent) { self::$encoder->endTag(); } // final top announcement for a multi-folder sync if ($sc->GetCollectionCount() > 1) { self::$topCollector->AnnounceInformation($this->getMultiFolderInfoLine($sc->GetCollectionCount()), true); } return true; }
public function testTagDeleteUnmodifiedItemChange() { $key = 'AAAAAAAA'; $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; // Create item via sync $data = '<data version="9"><items><item libraryID="' . self::$config['libraryID'] . '" itemType="book" ' . 'dateAdded="2009-03-07 04:53:20" ' . 'dateModified="2009-03-07 04:54:09" ' . 'key="' . $key . '"/></items>' . '<tags><tag libraryID="' . self::$config['libraryID'] . '" name="Test" ' . 'dateAdded="2009-03-07 04:54:56" ' . 'dateModified="2009-03-07 04:54:56" ' . 'key="BBBBBBBB">' . '<items>' . $key . '</items>' . '</tag></tags></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); // Get item via API $response = API::userGet(self::$config['userID'], "items/{$key}?key=" . self::$config['apiKey'] . "&content=json"); $xml = API::getXMLFromResponse($response); $data = API::parseDataFromAtomEntry($xml); $json = json_decode($data['content']); $originalVersion = $data['version']; $this->assertCount(1, $json->tags); $this->assertTrue(isset($json->tags[0]->tag)); $this->assertEquals("Test", $json->tags[0]->tag); // Get item via sync $xml = Sync::updated(self::$sessionID); $this->assertEquals(1, sizeOf($xml->updated->items->item)); $this->assertEquals(1, sizeOf($xml->updated->tags->tag)); $this->assertEquals(1, sizeOf($xml->updated->tags->tag[0]->items)); $lastsync = (int) $xml['timestamp']; usleep(1500000); // Increment the library version, since we're testing the // version below API::createItem('newspaperArticle', false, false, 'key'); $libraryVersion = API::getLibraryVersion(); $xml = Sync::updated(self::$sessionID); $updateKey = (string) $xml['updateKey']; // Delete tag via sync, with unmodified item $data = '<data version="9"><items><item libraryID="' . self::$config['libraryID'] . '" itemType="book" ' . 'dateAdded="2009-03-07 04:53:20" ' . 'dateModified="2009-03-07 04:54:09" ' . 'key="' . $key . '"/></items>' . '<deleted><tags><tag libraryID="' . self::$config['libraryID'] . '" key="BBBBBBBB"/>' . '</tags></deleted></data>'; $response = Sync::upload(self::$sessionID, $updateKey, $data); Sync::waitForUpload(self::$sessionID, $response, $this); // Get item via sync $xml = Sync::updated(self::$sessionID); $this->assertEquals(1, sizeOf(isset($xml->updated->tags->tag))); $this->assertFalse(isset($xml->updated->tags->tag[0]->items)); // Get item version via API $response = API::userGet(self::$config['userID'], "items/{$key}?key=" . self::$config['apiKey'] . "&content=json"); $xml = API::getXMLFromResponse($response); $data = API::parseDataFromAtomEntry($xml); $json = json_decode($data['content']); $this->assertEquals(0, (int) array_shift($xml->xpath('/atom:entry/zapi:numTags'))); $this->assertCount(0, $json->tags); // New item version should be greater than before $this->assertGreaterThan($originalVersion, $data['version']); // And should be one more than previous version $this->assertEquals($libraryVersion + 1, $data['version']); // Only the newspaperArticle should be updated $xml = Sync::updated(self::$sessionID, $lastsync); $this->assertEquals(1, $xml->updated[0]->items[0]->count()); }
<?php // Load module and PHP classes. include "example.php"; echo "Got new object\n"; echo "Got string {$s} and value {$x} \n"; $s = new Sync(); echo "Got new object\n"; $s->printer(); ?>
/** * @expectedException InvalidArgumentException */ function testException() { Sync::call(function () { throw new \InvalidArgumentException('Oops'); }); }
<?php /** * This small piece of php code calls the syncdata() function of Sync class. * @author Daan Janssens, mentored by Matthew Lagoe */ require '../../config.php'; require_once $AMS_LIB . '/libinclude.php'; Sync::syncdata();
session_cache_limiter('nocache'); session_start(); header("Expires: Mon, 01 May 2000 06:00:00 GMT"); header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); header("Cache-Control: max-age=1"); header("Cache-Control: no-store, no-cache, must-revalidate"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache"); // Running Cron if (isset($_GET["cron"])) { if ($_GET["cron"] == "true") { Sync::syncdata(false); } } // Always try to sync on page load, ie "lazy" cron Sync::syncdata(false); // Decide what page to load if (!isset($_GET["page"])) { if (isset($_SESSION['user'])) { if (Ticket_User::isMod(unserialize($_SESSION['ticket_user']))) { $page = 'dashboard'; } else { $page = 'show_user'; } } else { // default page $page = 'login'; } } else { // if the session exists load page with $_GET requests if (isset($_SESSION['user'])) {