Example #1
0
 public function deleted()
 {
     if ($this->apiVersion < 2) {
         $this->e404();
     }
     $this->allowMethods(array('GET'));
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
     // TEMP: sync transition
     if ($this->queryParams['sincetime'] !== null) {
         $deleted = array("collections" => Zotero_Collections::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "items" => Zotero_Items::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "searches" => Zotero_Searches::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "tags" => Zotero_Tags::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "settings" => Zotero_Settings::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true));
         echo Zotero_Utilities::formatJSON($deleted);
         $this->end();
     }
     if ($this->queryParams['since'] === null) {
         $this->e400("'since' parameter must be provided");
     }
     $deleted = array("collections" => Zotero_Collections::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "items" => Zotero_Items::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "searches" => Zotero_Searches::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "tags" => Zotero_Tags::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "settings" => Zotero_Settings::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']));
     echo Zotero_Utilities::formatJSON($deleted);
     $this->end();
 }
Example #2
0
 public function save($fixGaps = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID must be set before saving");
     }
     Zotero_Searches::editCheck($this);
     if (!$this->changed) {
         Z_Core::debug("Search {$this->id} has not changed");
         return false;
     }
     if (!isset($this->name) || $this->name === '') {
         throw new Exception("Name not provided for saved search");
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     $isNew = !$this->id || !$this->exists();
     try {
         $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches');
         Z_Core::debug("Saving search {$this->id}");
         if (!$isNew) {
             $sql = "DELETE FROM savedSearchConditions WHERE searchID=?";
             Zotero_DB::query($sql, $searchID, $shardID);
         }
         $key = $this->key ? $this->key : $this->generateKey();
         $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?";
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp);
         $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
         if ($isNew) {
             $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params));
             Zotero_Searches::cacheLibraryKeyID($this->libraryID, $key, $searchID);
             // Remove from delete log if it's there
             $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?";
             Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
         } else {
             $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID)));
         }
         // Close gaps in savedSearchIDs
         $saveConditions = array();
         $i = 1;
         foreach ($this->conditions as $id => $condition) {
             if (!$fixGaps && $id != $i) {
                 trigger_error('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' . $this->id, E_USER_ERROR);
             }
             $saveConditions[$i] = $condition;
             $i++;
         }
         $this->conditions = $saveConditions;
         // TODO: use proper bound parameters once DB class is updated
         foreach ($this->conditions as $searchConditionID => $condition) {
             $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)";
             $sqlParams = array($searchID, $searchConditionID, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', $condition['required'] ? 1 : 0);
             try {
                 Zotero_DB::query($sql, $sqlParams, $shardID);
             } catch (Exception $e) {
                 $msg = $e->getMessage();
                 if (strpos($msg, "Data too long for column 'value'") !== false) {
                     throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'");
                 }
                 throw $e;
             }
         }
         Zotero_DB::commit();
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     // If successful, set values in object
     if (!$this->id) {
         $this->id = $searchID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     return $this->id;
 }
Example #3
0
 private static function processUploadInternal($userID, SimpleXMLElement $xml, $syncQueueID = null, $syncProcessID = null)
 {
     $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
     $affectedLibraries = self::parseAffectedLibraries($xml->asXML());
     // Relations-only uploads don't have affected libraries
     if (!$affectedLibraries) {
         $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($userID));
     }
     $processID = self::addUploadProcess($userID, $affectedLibraries, $syncQueueID, $syncProcessID);
     set_time_limit(5400);
     $profile = false;
     if ($profile) {
         $shardID = Zotero_Shards::getByUserID($userID);
         Zotero_DB::profileStart($shardID);
     }
     try {
         Zotero_DB::beginTransaction();
         // Mark libraries as updated
         foreach ($affectedLibraries as $libraryID) {
             Zotero_Libraries::updateVersion($libraryID);
         }
         $timestamp = Zotero_Libraries::updateTimestamps($affectedLibraries);
         Zotero_DB::registerTransactionTimestamp($timestamp);
         // Make sure no other upload sessions use this same timestamp
         // for any of these libraries, since we return >= 1 as the next
         // last sync time
         if (!Zotero_Libraries::setTimestampLock($affectedLibraries, $timestamp)) {
             throw new Exception("Library timestamp already used", Z_ERROR_LIBRARY_TIMESTAMP_ALREADY_USED);
         }
         $modifiedItems = array();
         // Add/update creators
         if ($xml->creators) {
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->creators);
             $xmlElements = $xmlElements->getElementsByTagName('creator');
             Zotero_DB::query("SET foreign_key_checks = 0");
             try {
                 $addedLibraryIDs = array();
                 $addedCreatorDataHashes = array();
                 foreach ($xmlElements as $xmlElement) {
                     $key = $xmlElement->getAttribute('key');
                     if (isset($keys[$key])) {
                         throw new Exception("Creator {$key} already processed");
                     }
                     $keys[$key] = true;
                     $creatorObj = Zotero_Creators::convertXMLToCreator($xmlElement);
                     if (Zotero_Utilities::unicodeTrim($creatorObj->firstName) === '' && Zotero_Utilities::unicodeTrim($creatorObj->lastName) === '') {
                         continue;
                     }
                     $addedLibraryIDs[] = $creatorObj->libraryID;
                     $changed = $creatorObj->save($userID);
                     // If the creator changed, we need to update all linked items
                     if ($changed) {
                         $modifiedItems = array_merge($modifiedItems, $creatorObj->getLinkedItems());
                     }
                 }
             } catch (Exception $e) {
                 Zotero_DB::query("SET foreign_key_checks = 1");
                 throw $e;
             }
             Zotero_DB::query("SET foreign_key_checks = 1");
             unset($keys);
             unset($xml->creators);
             //
             // Manual foreign key checks
             //
             // libraryID
             foreach (array_unique($addedLibraryIDs) as $addedLibraryID) {
                 $shardID = Zotero_Shards::getByLibraryID($addedLibraryID);
                 $sql = "SELECT COUNT(*) FROM shardLibraries WHERE libraryID=?";
                 if (!Zotero_DB::valueQuery($sql, $addedLibraryID, $shardID)) {
                     throw new Exception("libraryID inserted into `creators` not found in `shardLibraries` ({$addedLibraryID}, {$shardID})");
                 }
             }
         }
         // Add/update items
         $savedItems = array();
         if ($xml->items) {
             $childItems = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->items);
             $xmlElements = $xmlElements->getElementsByTagName('item');
             foreach ($xmlElements as $xmlElement) {
                 $libraryID = (int) $xmlElement->getAttribute('libraryID');
                 $key = $xmlElement->getAttribute('key');
                 if (isset($savedItems[$libraryID . "/" . $key])) {
                     throw new Exception("Item {$libraryID}/{$key} already processed");
                 }
                 $itemObj = Zotero_Items::convertXMLToItem($xmlElement);
                 if (!$itemObj->getSourceKey()) {
                     try {
                         $modified = $itemObj->save($userID);
                         if ($modified) {
                             $savedItems[$libraryID . "/" . $key] = true;
                         }
                     } catch (Exception $e) {
                         if (strpos($e->getMessage(), 'libraryIDs_do_not_match') !== false) {
                             throw new Exception($e->getMessage() . " ({$key})");
                         }
                         throw $e;
                     }
                 } else {
                     $childItems[] = $itemObj;
                 }
             }
             unset($xml->items);
             while ($childItem = array_shift($childItems)) {
                 $libraryID = $childItem->libraryID;
                 $key = $childItem->key;
                 if (isset($savedItems[$libraryID . "/" . $key])) {
                     throw new Exception("Item {$libraryID}/{$key} already processed");
                 }
                 $modified = $childItem->save($userID);
                 if ($modified) {
                     $savedItems[$libraryID . "/" . $key] = true;
                 }
             }
         }
         // Add/update collections
         if ($xml->collections) {
             $collections = array();
             $collectionSets = array();
             // DOM
             // Build an array of unsaved collection objects and the keys of child items
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->collections);
             $xmlElements = $xmlElements->getElementsByTagName('collection');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Collection {$key} already processed");
                 }
                 $keys[$key] = true;
                 $collectionObj = Zotero_Collections::convertXMLToCollection($xmlElement);
                 $xmlItems = $xmlElement->getElementsByTagName('items')->item(0);
                 // Fix an error if there's leading or trailing whitespace,
                 // which was possible in 2.0.3
                 if ($xmlItems) {
                     $xmlItems = trim($xmlItems->nodeValue);
                 }
                 $arr = array('obj' => $collectionObj, 'items' => $xmlItems ? explode(' ', $xmlItems) : array());
                 $collections[] = $collectionObj;
                 $collectionSets[] = $arr;
             }
             unset($keys);
             unset($xml->collections);
             self::saveCollections($collections, $userID);
             unset($collections);
             // Set child items
             foreach ($collectionSets as $collection) {
                 // Child items
                 if (isset($collection['items'])) {
                     $ids = array();
                     foreach ($collection['items'] as $key) {
                         $item = Zotero_Items::getByLibraryAndKey($collection['obj']->libraryID, $key);
                         if (!$item) {
                             throw new Exception("Child item '{$key}' of collection {$collection['obj']->id} not found", Z_ERROR_ITEM_NOT_FOUND);
                         }
                         $ids[] = $item->id;
                     }
                     $collection['obj']->setItems($ids);
                 }
             }
             unset($collectionSets);
         }
         // Add/update saved searches
         if ($xml->searches) {
             $searches = array();
             $keys = array();
             foreach ($xml->searches->search as $xmlElement) {
                 $key = (string) $xmlElement['key'];
                 if (isset($keys[$key])) {
                     throw new Exception("Search {$key} already processed");
                 }
                 $keys[$key] = true;
                 $searchObj = Zotero_Searches::convertXMLToSearch($xmlElement);
                 $searchObj->save($userID);
             }
             unset($xml->searches);
         }
         // Add/update tags
         if ($xml->tags) {
             $keys = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->tags);
             $xmlElements = $xmlElements->getElementsByTagName('tag');
             foreach ($xmlElements as $xmlElement) {
                 // TEMP
                 $tagItems = $xmlElement->getElementsByTagName('items');
                 if ($tagItems->length && $tagItems->item(0)->nodeValue == "") {
                     error_log("Skipping tag with no linked items");
                     continue;
                 }
                 $libraryID = (int) $xmlElement->getAttribute('libraryID');
                 $key = $xmlElement->getAttribute('key');
                 $lk = $libraryID . "/" . $key;
                 if (isset($keys[$lk])) {
                     throw new Exception("Tag {$lk} already processed");
                 }
                 $keys[$lk] = true;
                 $itemKeysToUpdate = array();
                 $tagObj = Zotero_Tags::convertXMLToTag($xmlElement, $itemKeysToUpdate);
                 // We need to update removed items, added items, and,
                 // if the tag itself has changed, existing items
                 $modifiedItems = array_merge($modifiedItems, array_map(function ($key) use($libraryID) {
                     return $libraryID . "/" . $key;
                 }, $itemKeysToUpdate));
                 $tagObj->save($userID, true);
             }
             unset($keys);
             unset($xml->tags);
         }
         // Add/update relations
         if ($xml->relations) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->relations);
             $xmlElements = $xmlElements->getElementsByTagName('relation');
             foreach ($xmlElements as $xmlElement) {
                 $relationObj = Zotero_Relations::convertXMLToRelation($xmlElement, $userLibraryID);
                 if ($relationObj->exists()) {
                     continue;
                 }
                 $relationObj->save($userID);
             }
             unset($keys);
             unset($xml->relations);
         }
         // Add/update settings
         if ($xml->settings) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->settings);
             $xmlElements = $xmlElements->getElementsByTagName('setting');
             foreach ($xmlElements as $xmlElement) {
                 $settingObj = Zotero_Settings::convertXMLToSetting($xmlElement);
                 $settingObj->save($userID);
             }
             unset($xml->settings);
         }
         if ($xml->fulltexts) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->fulltexts);
             $xmlElements = $xmlElements->getElementsByTagName('fulltext');
             foreach ($xmlElements as $xmlElement) {
                 Zotero_FullText::indexFromXML($xmlElement, $userID);
             }
             unset($xml->fulltexts);
         }
         // TODO: loop
         if ($xml->deleted) {
             // Delete collections
             if ($xml->deleted->collections) {
                 Zotero_Collections::deleteFromXML($xml->deleted->collections, $userID);
             }
             // Delete items
             if ($xml->deleted->items) {
                 Zotero_Items::deleteFromXML($xml->deleted->items, $userID);
             }
             // Delete creators
             if ($xml->deleted->creators) {
                 Zotero_Creators::deleteFromXML($xml->deleted->creators, $userID);
             }
             // Delete saved searches
             if ($xml->deleted->searches) {
                 Zotero_Searches::deleteFromXML($xml->deleted->searches, $userID);
             }
             // Delete tags
             if ($xml->deleted->tags) {
                 $xmlElements = dom_import_simplexml($xml->deleted->tags);
                 $xmlElements = $xmlElements->getElementsByTagName('tag');
                 foreach ($xmlElements as $xmlElement) {
                     $libraryID = (int) $xmlElement->getAttribute('libraryID');
                     $key = $xmlElement->getAttribute('key');
                     $tagObj = Zotero_Tags::getByLibraryAndKey($libraryID, $key);
                     if (!$tagObj) {
                         continue;
                     }
                     // We need to update all items on the deleted tag
                     $modifiedItems = array_merge($modifiedItems, array_map(function ($key) use($libraryID) {
                         return $libraryID . "/" . $key;
                     }, $tagObj->getLinkedItems(true)));
                 }
                 Zotero_Tags::deleteFromXML($xml->deleted->tags, $userID);
             }
             // Delete relations
             if ($xml->deleted->relations) {
                 Zotero_Relations::deleteFromXML($xml->deleted->relations, $userID);
             }
             // Delete relations
             if ($xml->deleted->settings) {
                 Zotero_Settings::deleteFromXML($xml->deleted->settings, $userID);
             }
         }
         $toUpdate = array();
         foreach ($modifiedItems as $item) {
             // libraryID/key string
             if (is_string($item)) {
                 if (isset($savedItems[$item])) {
                     continue;
                 }
                 $savedItems[$item] = true;
                 list($libraryID, $key) = explode("/", $item);
                 $item = Zotero_Items::getByLibraryAndKey($libraryID, $key);
                 if (!$item) {
                     // Item was deleted
                     continue;
                 }
             } else {
                 $lk = $item->libraryID . "/" . $item->key;
                 if (isset($savedItems[$lk])) {
                     continue;
                 }
                 $savedItems[$lk] = true;
             }
             $toUpdate[] = $item;
         }
         Zotero_Items::updateVersions($toUpdate, $userID);
         unset($savedItems);
         unset($modifiedItems);
         try {
             self::removeUploadProcess($processID);
         } catch (Exception $e) {
             if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
                 // Reconnect
                 error_log("Reconnecting to MySQL master");
                 Zotero_DB::close();
                 self::removeUploadProcess($processID);
             } else {
                 throw $e;
             }
         }
         // Send notifications for changed libraries
         foreach ($affectedLibraries as $libraryID) {
             Zotero_Notifier::trigger('modify', 'library', $libraryID);
         }
         Zotero_DB::commit();
         if ($profile) {
             $shardID = Zotero_Shards::getByUserID($userID);
             Zotero_DB::profileEnd($shardID);
         }
         // Return timestamp + 1, to keep the next /updated call
         // (using >= timestamp) from returning this data
         return $timestamp + 1;
     } catch (Exception $e) {
         Zotero_DB::rollback(true);
         self::removeUploadProcess($processID);
         throw $e;
     }
 }
Example #4
0
 public function save($userID = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID must be set before saving");
     }
     Zotero_Searches::editCheck($this, $userID);
     if (!$this->changed) {
         Z_Core::debug("Search {$this->id} has not changed");
         return false;
     }
     if (!isset($this->name) || $this->name === '') {
         throw new Exception("Name not provided for saved search");
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     $isNew = !$this->id || !$this->exists();
     try {
         $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches');
         Z_Core::debug("Saving search {$this->id}");
         if (!$isNew) {
             $sql = "DELETE FROM savedSearchConditions WHERE searchID=?";
             Zotero_DB::query($sql, $searchID, $shardID);
         }
         $key = $this->key ? $this->key : Zotero_ID::getKey();
         $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?, version=?";
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp, Zotero_Libraries::getUpdatedVersion($this->libraryID));
         $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
         if ($isNew) {
             $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params));
             // Remove from delete log if it's there
             $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?";
             Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
         } else {
             $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID)));
         }
         foreach ($this->conditions as $searchConditionID => $condition) {
             $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)";
             $sqlParams = array($searchID, $searchConditionID + 1, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', !empty($condition['required']) ? 1 : 0);
             try {
                 Zotero_DB::query($sql, $sqlParams, $shardID);
             } catch (Exception $e) {
                 $msg = $e->getMessage();
                 if (strpos($msg, "Data too long for column 'value'") !== false) {
                     throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'");
                 }
                 throw $e;
             }
         }
         Zotero_DB::commit();
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     if (!$this->id) {
         $this->id = $searchID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     return $this->id;
 }
 public function searches()
 {
     if ($this->apiVersion < 2) {
         $this->e404();
     }
     // Check for general library access
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     if ($this->isWriteMethod()) {
         // Check for library write access
         if (!$this->permissions->canWrite($this->objectLibraryID)) {
             $this->e403("Write access denied");
         }
         // Make sure library hasn't been modified
         if (!$this->singleObject) {
             $libraryTimestampChecked = $this->checkLibraryIfUnmodifiedSinceVersion();
         }
         Zotero_Libraries::updateVersionAndTimestamp($this->objectLibraryID);
     }
     $results = array();
     // Single search
     if ($this->singleObject) {
         $this->allowMethods(['HEAD', 'GET', 'PUT', 'PATCH', 'DELETE']);
         $search = Zotero_Searches::getByLibraryAndKey($this->objectLibraryID, $this->objectKey);
         if ($this->isWriteMethod()) {
             $search = $this->handleObjectWrite('search', $search ? $search : null);
             $this->e204();
         }
         if (!$search) {
             $this->e404("Search not found");
         }
         $this->libraryVersion = $search->version;
         if ($this->method == 'HEAD') {
             $this->end();
         }
         // Display search
         switch ($this->queryParams['format']) {
             case 'atom':
                 $this->responseXML = $search->toAtom($this->queryParams);
                 break;
             case 'json':
                 $json = $search->toResponseJSON($this->queryParams, $this->permissions);
                 echo Zotero_Utilities::formatJSON($json);
                 break;
             default:
                 throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'");
         }
     } else {
         $this->allowMethods(['HEAD', 'GET', 'POST', 'DELETE']);
         $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
         // Create a search
         if ($this->method == 'POST') {
             $this->queryParams['format'] = 'writereport';
             $obj = $this->jsonDecode($this->body);
             $results = Zotero_Searches::updateMultipleFromJSON($obj, $this->objectLibraryID, $this->queryParams, $this->userID, $libraryTimestampChecked ? 0 : 1, null);
             if ($cacheKey = $this->getWriteTokenCacheKey()) {
                 Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime);
             }
         } else {
             if ($this->method == 'DELETE') {
                 Zotero_DB::beginTransaction();
                 foreach ($this->queryParams['searchKey'] as $searchKey) {
                     Zotero_Searches::delete($this->objectLibraryID, $searchKey);
                 }
                 Zotero_DB::commit();
                 $this->e204();
             } else {
                 $title = "Searches";
                 $results = Zotero_Searches::search($this->objectLibraryID, $this->queryParams);
             }
         }
         $options = ['action' => $this->action, 'uri' => $this->uri, 'results' => $results, 'requestParams' => $this->queryParams, 'permissions' => $this->permissions, 'head' => $this->method == 'HEAD'];
         switch ($this->queryParams['format']) {
             case 'atom':
                 $this->responseXML = Zotero_API::multiResponse(array_merge($options, ['title' => $this->getFeedNamePrefix($this->objectLibraryID) . $title]));
                 break;
             case 'json':
             case 'keys':
             case 'versions':
             case 'writereport':
                 Zotero_API::multiResponse($options);
                 break;
             default:
                 throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'");
         }
     }
     $this->end();
 }
Example #6
0
 private static function processUploadInternal($userID, SimpleXMLElement $xml, $syncQueueID = null, $syncProcessID = null)
 {
     $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
     $affectedLibraries = self::parseAffectedLibraries($xml->asXML());
     // Relations-only uploads don't have affected libraries
     if (!$affectedLibraries) {
         $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($userID));
     }
     $processID = self::addUploadProcess($userID, $affectedLibraries, $syncQueueID, $syncProcessID);
     set_time_limit(5400);
     $profile = false;
     if ($profile) {
         $shardID = Zotero_Shards::getByUserID($userID);
         Zotero_DB::profileStart($shardID);
     }
     try {
         Z_Core::$MC->begin();
         Zotero_DB::beginTransaction();
         // Mark libraries as updated
         $timestamp = Zotero_Libraries::updateTimestamps($affectedLibraries);
         Zotero_DB::registerTransactionTimestamp($timestamp);
         // Make sure no other upload sessions use this same timestamp
         // for any of these libraries, since we return >= 1 as the next
         // last sync time
         if (!Zotero_Libraries::setTimestampLock($affectedLibraries, $timestamp)) {
             throw new Exception("Library timestamp already used", Z_ERROR_LIBRARY_TIMESTAMP_ALREADY_USED);
         }
         // Add/update creators
         if ($xml->creators) {
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->creators);
             $xmlElements = $xmlElements->getElementsByTagName('creator');
             Zotero_DB::query("SET foreign_key_checks = 0");
             try {
                 $addedLibraryIDs = array();
                 $addedCreatorDataHashes = array();
                 foreach ($xmlElements as $xmlElement) {
                     $key = $xmlElement->getAttribute('key');
                     if (isset($keys[$key])) {
                         throw new Exception("Creator {$key} already processed");
                     }
                     $keys[$key] = true;
                     $creatorObj = Zotero_Creators::convertXMLToCreator($xmlElement);
                     $addedLibraryIDs[] = $creatorObj->libraryID;
                     $creatorObj->save();
                 }
             } catch (Exception $e) {
                 Zotero_DB::query("SET foreign_key_checks = 1");
                 throw $e;
             }
             Zotero_DB::query("SET foreign_key_checks = 1");
             unset($keys);
             unset($xml->creators);
             //
             // Manual foreign key checks
             //
             // libraryID
             foreach ($addedLibraryIDs as $addedLibraryID) {
                 $shardID = Zotero_Shards::getByLibraryID($addedLibraryID);
                 $sql = "SELECT COUNT(*) FROM shardLibraries WHERE libraryID=?";
                 if (!Zotero_DB::valueQuery($sql, $addedLibraryID, $shardID)) {
                     throw new Exception("libraryID inserted into `creators` not found in `shardLibraries` ({$addedLibraryID}, {$shardID})");
                 }
             }
         }
         // Add/update items
         if ($xml->items) {
             $childItems = array();
             $relatedItemsStore = array();
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->items);
             $xmlElements = $xmlElements->getElementsByTagName('item');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Item {$key} already processed");
                 }
                 $keys[$key] = true;
                 $missing = Zotero_Items::removeMissingRelatedItems($xmlElement);
                 $itemObj = Zotero_Items::convertXMLToItem($xmlElement);
                 if ($missing) {
                     $relatedItemsStore[$itemObj->libraryID . '_' . $itemObj->key] = $missing;
                 }
                 if (!$itemObj->getSourceKey()) {
                     try {
                         $itemObj->save($userID);
                     } catch (Exception $e) {
                         if (strpos($e->getMessage(), 'libraryIDs_do_not_match') !== false) {
                             throw new Exception($e->getMessage() . " (" . $itemObj->key . ")");
                         }
                         throw $e;
                     }
                 } else {
                     $childItems[] = $itemObj;
                 }
             }
             unset($keys);
             unset($xml->items);
             while ($childItem = array_shift($childItems)) {
                 $childItem->save($userID);
             }
             // Add back related items (which now exist)
             foreach ($relatedItemsStore as $itemLibraryKey => $relset) {
                 $lk = explode('_', $itemLibraryKey);
                 $libraryID = $lk[0];
                 $key = $lk[1];
                 $item = Zotero_Items::getByLibraryAndKey($libraryID, $key);
                 foreach ($relset as $relKey) {
                     $relItem = Zotero_Items::getByLibraryAndKey($libraryID, $relKey);
                     $item->addRelatedItem($relItem->id);
                 }
                 $item->save();
             }
             unset($relatedItemsStore);
         }
         // Add/update collections
         if ($xml->collections) {
             $collections = array();
             $collectionSets = array();
             // DOM
             // Build an array of unsaved collection objects and the keys of child items
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->collections);
             $xmlElements = $xmlElements->getElementsByTagName('collection');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Collection {$key} already processed");
                 }
                 $keys[$key] = true;
                 $collectionObj = Zotero_Collections::convertXMLToCollection($xmlElement);
                 $xmlItems = $xmlElement->getElementsByTagName('items')->item(0);
                 // Fix an error if there's leading or trailing whitespace,
                 // which was possible in 2.0.3
                 if ($xmlItems) {
                     $xmlItems = trim($xmlItems->nodeValue);
                 }
                 $arr = array('obj' => $collectionObj, 'items' => $xmlItems ? explode(' ', $xmlItems) : array());
                 $collections[] = $collectionObj;
                 $collectionSets[] = $arr;
             }
             unset($keys);
             unset($xml->collections);
             self::saveCollections($collections);
             unset($collections);
             // Set child items
             foreach ($collectionSets as $collection) {
                 // Child items
                 if (isset($collection['items'])) {
                     $ids = array();
                     foreach ($collection['items'] as $key) {
                         $item = Zotero_Items::getByLibraryAndKey($collection['obj']->libraryID, $key);
                         if (!$item) {
                             throw new Exception("Child item '{$key}' of collection {$collection['obj']->id} not found", Z_ERROR_ITEM_NOT_FOUND);
                         }
                         $ids[] = $item->id;
                     }
                     $collection['obj']->setChildItems($ids);
                 }
             }
             unset($collectionSets);
         }
         // Add/update saved searches
         if ($xml->searches) {
             $searches = array();
             $keys = array();
             foreach ($xml->searches->search as $xmlElement) {
                 $key = (string) $xmlElement['key'];
                 if (isset($keys[$key])) {
                     throw new Exception("Search {$key} already processed");
                 }
                 $keys[$key] = true;
                 $searchObj = Zotero_Searches::convertXMLToSearch($xmlElement);
                 $searchObj->save();
             }
             unset($xml->searches);
         }
         // Add/update tags
         if ($xml->tags) {
             $keys = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->tags);
             $xmlElements = $xmlElements->getElementsByTagName('tag');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Tag {$key} already processed");
                 }
                 $keys[$key] = true;
                 $tagObj = Zotero_Tags::convertXMLToTag($xmlElement);
                 $tagObj->save(true);
             }
             unset($keys);
             unset($xml->tags);
         }
         // Add/update relations
         if ($xml->relations) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->relations);
             $xmlElements = $xmlElements->getElementsByTagName('relation');
             foreach ($xmlElements as $xmlElement) {
                 $relationObj = Zotero_Relations::convertXMLToRelation($xmlElement, $userLibraryID);
                 if ($relationObj->exists()) {
                     continue;
                 }
                 $relationObj->save();
             }
             unset($keys);
             unset($xml->relations);
         }
         // TODO: loop
         if ($xml->deleted) {
             // Delete collections
             if ($xml->deleted->collections) {
                 Zotero_Collections::deleteFromXML($xml->deleted->collections);
             }
             // Delete items
             if ($xml->deleted->items) {
                 Zotero_Items::deleteFromXML($xml->deleted->items);
             }
             // Delete creators
             if ($xml->deleted->creators) {
                 Zotero_Creators::deleteFromXML($xml->deleted->creators);
             }
             // Delete saved searches
             if ($xml->deleted->searches) {
                 Zotero_Searches::deleteFromXML($xml->deleted->searches);
             }
             // Delete tags
             if ($xml->deleted->tags) {
                 Zotero_Tags::deleteFromXML($xml->deleted->tags);
             }
             // Delete tags
             if ($xml->deleted->relations) {
                 Zotero_Relations::deleteFromXML($xml->deleted->relations);
             }
         }
         self::removeUploadProcess($processID);
         Zotero_DB::commit();
         Z_Core::$MC->commit();
         if ($profile) {
             $shardID = Zotero_Shards::getByUserID($userID);
             Zotero_DB::profileEnd($shardID);
         }
         // Return timestamp + 1, to keep the next /updated call
         // (using >= timestamp) from returning this data
         return $timestamp + 1;
     } catch (Exception $e) {
         Z_Core::$MC->rollback();
         Zotero_DB::rollback(true);
         self::removeUploadProcess($processID);
         throw $e;
     }
 }
Example #7
0
 public static function createAtomFeed($title, $url, $entries, $totalResults = null, $queryParams = null, $apiVersion = null, $permissions = null, $fixedValues = array())
 {
     if ($queryParams) {
         $nonDefaultParams = Zotero_API::getNonDefaultQueryParams($queryParams);
         // Convert 'content' array to sorted comma-separated string
         if (isset($nonDefaultParams['content'])) {
             $nonDefaultParams['content'] = implode(',', $nonDefaultParams['content']);
         }
     } else {
         $nonDefaultParams = array();
     }
     $feed = '<feed xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"';
     if ($queryParams && $queryParams['content'][0] == 'full') {
         $feed .= ' xmlns:zxfer="' . Zotero_Atom::$nsZoteroTransfer . '"';
     }
     $feed .= '/>';
     $xml = new SimpleXMLElement($feed);
     $xml->title = $title;
     $path = parse_url($url, PHP_URL_PATH);
     // Generate canonical URI
     $zoteroURI = Zotero_URI::getBaseURI() . substr($path, 1);
     if ($nonDefaultParams) {
         $zoteroURI .= "?" . http_build_query($nonDefaultParams);
     }
     $atomURI = Zotero_Atom::getBaseURI() . substr($path, 1);
     //
     // Generate URIs for 'self', 'first', 'next' and 'last' links
     //
     // 'self'
     $atomSelfURI = $atomURI;
     if ($nonDefaultParams) {
         $atomSelfURI .= "?" . http_build_query($nonDefaultParams);
     }
     // 'first'
     $atomFirstURI = $atomURI;
     if ($nonDefaultParams) {
         $p = $nonDefaultParams;
         unset($p['start']);
         if ($first = http_build_query($p)) {
             $atomFirstURI .= "?" . $first;
         }
     }
     // 'last'
     if (!$queryParams['start'] && $queryParams['limit'] >= $totalResults) {
         $atomLastURI = $atomSelfURI;
     } else {
         // 'start' past results
         if ($queryParams['start'] >= $totalResults) {
             $lastStart = $totalResults - $queryParams['limit'];
         } else {
             $lastStart = $totalResults - $totalResults % $queryParams['limit'];
             if ($lastStart == $totalResults) {
                 $lastStart = $totalResults - $queryParams['limit'];
             }
         }
         $p = $nonDefaultParams;
         if ($lastStart > 0) {
             $p['start'] = $lastStart;
         } else {
             unset($p['start']);
         }
         $atomLastURI = $atomURI;
         if ($last = http_build_query($p)) {
             $atomLastURI .= "?" . $last;
         }
         // 'next'
         $nextStart = $queryParams['start'] + $queryParams['limit'];
         if ($nextStart < $totalResults) {
             $p = $nonDefaultParams;
             $p['start'] = $nextStart;
             $atomNextURI = $atomURI . "?" . http_build_query($p);
         }
     }
     $xml->id = $zoteroURI;
     $link = $xml->addChild("link");
     $link['rel'] = "self";
     $link['type'] = "application/atom+xml";
     $link['href'] = $atomSelfURI;
     $link = $xml->addChild("link");
     $link['rel'] = "first";
     $link['type'] = "application/atom+xml";
     $link['href'] = $atomFirstURI;
     if (isset($atomNextURI)) {
         $link = $xml->addChild("link");
         $link['rel'] = "next";
         $link['type'] = "application/atom+xml";
         $link['href'] = $atomNextURI;
     }
     $link = $xml->addChild("link");
     $link['rel'] = "last";
     $link['type'] = "application/atom+xml";
     $link['href'] = $atomLastURI;
     // Generate alternate URI
     $alternateURI = Zotero_URI::getBaseURI() . substr($path, 1);
     if ($nonDefaultParams) {
         $p = $nonDefaultParams;
         if (isset($p['content'])) {
             unset($p['content']);
         }
         if ($p) {
             $alternateURI .= "?" . http_build_query($p);
         }
     }
     $link = $xml->addChild("link");
     $link['rel'] = "alternate";
     $link['type'] = "text/html";
     $link['href'] = $alternateURI;
     $xml->addChild("zapi:totalResults", is_numeric($totalResults) ? $totalResults : sizeOf($entries), self::$nsZoteroAPI);
     $xml->addChild("zapi:apiVersion", $apiVersion, self::$nsZoteroAPI);
     $latestUpdated = '';
     // Get bib data using parallel requests
     $sharedData = array();
     if ($entries && $entries[0] instanceof Zotero_Item) {
         if (in_array('citation', $queryParams['content'])) {
             $sharedData["citation"] = Zotero_Cite::multiGetFromCiteServer("citation", $entries, $queryParams['style']);
         }
         if (in_array('bib', $queryParams['content'])) {
             $sharedData["bib"] = Zotero_Cite::multiGetFromCiteServer("bib", $entries, $queryParams['style']);
         }
     }
     $xmlEntries = array();
     foreach ($entries as $entry) {
         if ($entry->dateModified > $latestUpdated) {
             $latestUpdated = $entry->dateModified;
         }
         if ($entry instanceof SimpleXMLElement) {
             $xmlEntries[] = $entry;
         } else {
             if ($entry instanceof Zotero_Collection) {
                 $entry = Zotero_Collections::convertCollectionToAtom($entry, $queryParams['content'], $apiVersion);
                 $xmlEntries[] = $entry;
             } else {
                 if ($entry instanceof Zotero_Creator) {
                     $entry = Zotero_Creators::convertCreatorToAtom($entry, $queryParams['content'], $apiVersion);
                     $xmlEntries[] = $entry;
                 } else {
                     if ($entry instanceof Zotero_Item) {
                         $entry = Zotero_Items::convertItemToAtom($entry, $queryParams, $apiVersion, $permissions, $sharedData);
                         $xmlEntries[] = $entry;
                     } else {
                         if ($entry instanceof Zotero_Search) {
                             $entry = Zotero_Searches::convertSearchToAtom($entry, $queryParams['content'], $apiVersion);
                             $xmlEntries[] = $entry;
                         } else {
                             if ($entry instanceof Zotero_Tag) {
                                 $xmlEntries[] = $entry->toAtom($queryParams['content'], $apiVersion, isset($fixedValues[$entry->id]) ? $fixedValues[$entry->id] : null);
                             } else {
                                 if ($entry instanceof Zotero_Group) {
                                     $entry = $entry->toAtom($queryParams['content'], $apiVersion);
                                     $xmlEntries[] = $entry;
                                 }
                             }
                         }
                     }
                 }
             }
         }
     }
     if ($latestUpdated) {
         $xml->updated = Zotero_Date::sqlToISO8601($latestUpdated);
     } else {
         $xml->updated = str_replace("+00:00", "Z", date('c'));
     }
     // Import object XML nodes into document
     $doc = dom_import_simplexml($xml);
     foreach ($xmlEntries as $xmlEntry) {
         $subNode = dom_import_simplexml($xmlEntry);
         $importedNode = $doc->ownerDocument->importNode($subNode, true);
         $doc->appendChild($importedNode);
     }
     return $xml;
 }
Example #8
0
                                    }
                                    break;
                                case 'operator':
                                    if ($conditionVal === "") {
                                        throw new Exception("Search operator cannot be empty", Z_ERROR_INVALID_INPUT);
                                    }
                                    $maxLen = 25;
                                    if (strlen($conditionVal) > $maxLen) {
                                        throw new Exception("Search operator cannot be longer than {$maxLen} characters", Z_ERROR_INVALID_INPUT);
                                    }
                                    break;
                                case 'value':
                                    $maxLen = 255;
                                    if (strlen($conditionVal) > $maxLen) {
                                        throw new Exception("Search operator cannot be longer than {$maxLen} characters", Z_ERROR_INVALID_INPUT);
                                    }
                                    break;
                                default:
                                    throw new Exception("Invalid property '{$conditionKey}' for search condition", Z_ERROR_INVALID_INPUT);
                            }
                        }
                    }
                    break;
                default:
                    throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
            }
        }
    }
}
Zotero_Searches::init();