Esempio n. 1
0
 public static function add($userID)
 {
     Z_Core::debug("Creating publications library for user {$userID}");
     Zotero_DB::beginTransaction();
     // Use same shard as user library
     $shardID = Zotero_Shards::getByUserID($userID);
     $libraryID = Zotero_Libraries::add('publications', $shardID);
     $sql = "INSERT INTO userPublications (userID, libraryID) VALUES (?, ?)";
     Zotero_DB::query($sql, [$userID, $libraryID]);
     Zotero_DB::commit();
     return $libraryID;
 }
Esempio n. 2
0
 public static function getLibraryURI($libraryID, $skipNames = false)
 {
     $libraryType = Zotero_Libraries::getType($libraryID);
     switch ($libraryType) {
         case 'user':
             $id = Zotero_Users::getUserIDFromLibraryID($libraryID);
             return self::getUserURI($id, $skipNames);
         case 'group':
             $id = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
             $group = Zotero_Groups::get($id);
             return self::getGroupURI($group, $skipNames);
     }
 }
Esempio n. 3
0
 public function laststoragesync()
 {
     $this->allowMethods(array('GET', 'POST'));
     // Deprecated after 3.0, which used auth=1
     if ($this->apiVersion < 2 || !empty($_GET['auth'])) {
         $lastSync = Zotero_Users::getLastStorageSync($this->objectUserID);
     } else {
         $lastSync = Zotero_Libraries::getLastStorageSync($this->objectLibraryID);
     }
     if (!$lastSync) {
         $this->e404();
     }
     echo $lastSync;
     exit;
 }
Esempio n. 4
0
 public static function getLibraryURI($libraryID, $www = false, $useSlug = false)
 {
     $libraryType = Zotero_Libraries::getType($libraryID);
     switch ($libraryType) {
         case 'user':
             $id = Zotero_Users::getUserIDFromLibraryID($libraryID);
             return self::getUserURI($id, $www, $useSlug);
         case 'publications':
             $id = Zotero_Users::getUserIDFromLibraryID($libraryID);
             return self::getUserURI($id, $www, $useSlug) . "/publications";
         case 'group':
             $id = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
             $group = Zotero_Groups::get($id);
             return self::getGroupURI($group, $www, $useSlug);
         default:
             throw new Exception("Invalid library type '{$libraryType}'");
     }
 }
Esempio n. 5
0
 public static function getUserKeysWithLibrary($userID, $libraryID)
 {
     $libraryType = Zotero_Libraries::getType($libraryID);
     $sql = "SELECT keyID FROM `keys` JOIN keyPermissions USING (keyID) " . "WHERE userID=? AND (libraryID=?";
     // If group library, include keys with access to all groups
     if ($libraryType == 'group') {
         $sql .= " OR libraryID=0";
     }
     $sql .= ") AND permission='library' AND granted=1";
     $keyIDs = Zotero_DB::columnQuery($sql, [$userID, $libraryID]);
     $keys = [];
     if ($keyIDs) {
         foreach ($keyIDs as $keyID) {
             $keyObj = new Zotero_Key();
             $keyObj->id = $keyID;
             $keys[] = $keyObj;
         }
     }
     return $keys;
 }
Esempio n. 6
0
 public function laststoragesync()
 {
     if (!$this->httpAuth && !$this->permissions->isSuper()) {
         $this->e403();
     }
     $this->allowMethods(array('GET', 'POST'));
     if ($this->method == 'POST') {
         //Zotero_Users::setLastStorageSync($this->userID);
     }
     // Deprecated after 3.0, which used auth=1
     if ($this->apiVersion < 2 || !empty($_GET['auth'])) {
         $lastSync = Zotero_Users::getLastStorageSync($this->objectUserID);
     } else {
         $lastSync = Zotero_Libraries::getLastStorageSync($this->objectLibraryID);
     }
     if (!$lastSync) {
         $this->e404();
     }
     echo $lastSync;
     exit;
 }
Esempio n. 7
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();
 }
Esempio n. 8
0
 public static function updateFileItemInfo($item, $storageFileID, Zotero_StorageFileInfo $info, $client = false)
 {
     if (!$item->isImportedAttachment()) {
         throw new Exception("Cannot add storage file for linked file/URL");
     }
     Zotero_DB::beginTransaction();
     if (!$client) {
         Zotero_Libraries::updateVersionAndTimestamp($item->libraryID);
     }
     self::updateLastAdded($storageFileID);
     // Note: We set the size on the shard so that usage queries are instantaneous
     $sql = "INSERT INTO storageFileItems (storageFileID, itemID, mtime, size) VALUES (?,?,?,?)\n\t\t\t\tON DUPLICATE KEY UPDATE storageFileID=?, mtime=?, size=?";
     Zotero_DB::query($sql, array($storageFileID, $item->id, $info->mtime, $info->size, $storageFileID, $info->mtime, $info->size), Zotero_Shards::getByLibraryID($item->libraryID));
     // 4.0 client doesn't set filename for ZIP files
     if (!$info->zip || !empty($info->itemFilename)) {
         $item->attachmentFilename = !empty($info->itemFilename) ? $info->itemFilename : $info->filename;
     }
     $item->attachmentStorageHash = !empty($info->itemHash) ? $info->itemHash : $info->hash;
     $item->attachmentStorageModTime = $info->mtime;
     // contentType and charset may not have been included in the
     // upload authorization, in which case we shouldn't overwrite
     // any values that may already be set on the attachment
     if (isset($info->contentType)) {
         $item->attachmentMIMEType = $info->contentType;
     }
     if (isset($info->charset)) {
         $item->attachmentCharset = $info->charset;
     }
     $item->save();
     Zotero_DB::commit();
 }
Esempio n. 9
0
 public static function copyLibrary($libraryID, $newShardID, $overrideLock = false)
 {
     $currentShardID = self::getByLibraryID($libraryID);
     if ($currentShardID == $newShardID) {
         throw new Exception("Library {$libraryID} is already on shard {$newShardID}");
     }
     if (!self::shardIsWriteable($newShardID)) {
         throw new Exception("Shard {$newShardID} is not writeable");
     }
     if (!$overrideLock && Zotero_Libraries::isLocked($libraryID)) {
         throw new Exception("Library {$libraryID} is locked");
     }
     // Make sure there's no stale data on the new shard
     if (self::checkForLibrary($libraryID, $newShardID)) {
         throw new Exception("Library {$libraryID} data already exists on shard {$newShardID}");
     }
     Zotero_DB::beginTransaction();
     Zotero_DB::query("SET foreign_key_checks=0", false, $newShardID);
     $tables = array('shardLibraries', 'collections', 'creators', 'items', 'relations', 'savedSearches', 'tags', 'collectionItems', 'deletedItems', 'groupItems', 'itemAttachments', 'itemCreators', 'itemData', 'itemNotes', 'itemRelated', 'itemSortFields', 'itemTags', 'savedSearchConditions', 'storageFileItems', 'syncDeleteLogIDs', 'syncDeleteLogKeys');
     foreach ($tables as $table) {
         if (!$overrideLock && Zotero_Libraries::isLocked($libraryID)) {
             Zotero_DB::rollback();
             throw new Exception("Aborted due to library lock");
         }
         switch ($table) {
             case 'collections':
             case 'creators':
             case 'items':
             case 'relations':
             case 'savedSearches':
             case 'shardLibraries':
             case 'syncDeleteLogIDs':
             case 'syncDeleteLogKeys':
             case 'tags':
                 $sql = "SELECT * FROM {$table} WHERE libraryID=?";
                 break;
             case 'collectionItems':
                 $sql = "SELECT CI.* FROM collectionItems CI\n\t\t\t\t\t\t\tJOIN collections USING (collectionID) WHERE libraryID=?";
                 break;
             case 'deletedItems':
             case 'groupItems':
             case 'itemAttachments':
             case 'itemCreators':
             case 'itemData':
             case 'itemNotes':
             case 'itemRelated':
             case 'itemSortFields':
             case 'itemTags':
             case 'storageFileItems':
                 $sql = "SELECT T.* FROM {$table} T JOIN items USING (itemID) WHERE libraryID=?";
                 break;
             case 'savedSearchConditions':
                 $sql = "SELECT SSC.* FROM savedSearchConditions SSC\n\t\t\t\t\t\t\tJOIN savedSearches USING (searchID) WHERE libraryID=?";
                 break;
         }
         $rows = Zotero_DB::query($sql, $libraryID, $currentShardID);
         if ($rows) {
             $sets = array();
             foreach ($rows as $row) {
                 $sets[] = array_values($row);
             }
             $sql = "INSERT INTO {$table} VALUES ";
             Zotero_DB::bulkInsert($sql, $sets, 50, false, $newShardID);
         }
     }
     Zotero_DB::query("SET foreign_key_checks=1", false, $newShardID);
     if (!$overrideLock && Zotero_Libraries::isLocked($libraryID)) {
         Zotero_DB::rollback();
         throw new Exception("Aborted due to library lock");
     }
     Zotero_DB::commit();
     if (!$overrideLock && Zotero_Libraries::isLocked($libraryID)) {
         self::deleteLibrary($libraryID, $newShardID);
         throw new Exception("Aborted due to library lock");
     }
 }
Esempio n. 10
0
 protected function end()
 {
     if ($this->profile) {
         Zotero_DB::profileEnd($this->objectLibraryID, true);
     }
     switch ($this->responseCode) {
         case 200:
             // Output a Content-Type header for the given format
             //
             // Note that this overrides any Content-Type set elsewhere. To force a content
             // type elsewhere, clear $this->queryParams['format'] when calling header()
             // manually.
             //
             // TODO: Check headers_list so that clearing the format parameter manually isn't
             // necessary? Performance?
             if (isset($this->queryParams['format'])) {
                 Zotero_API::outputContentType($this->queryParams['format']);
             }
             break;
         case 301:
         case 302:
         case 303:
             // Handled in $this->redirect()
             break;
         case 401:
             header('WWW-Authenticate: Basic realm="Zotero API"');
             header('HTTP/1.1 401 Unauthorized');
             break;
             // PHP completes these automatically
         // PHP completes these automatically
         case 201:
         case 204:
         case 300:
         case 304:
         case 400:
         case 403:
         case 404:
         case 405:
         case 409:
         case 412:
         case 413:
         case 422:
         case 500:
         case 501:
         case 503:
             header("HTTP/1.1 " . $this->responseCode);
             break;
         case 428:
             header("HTTP/1.1 428 Precondition Required");
             break;
         case 429:
             header("HTTP/1.1 429 Too Many Requests");
             break;
         default:
             throw new Exception("Unsupported response code " . $this->responseCode);
     }
     if (isset($this->libraryVersion)) {
         if ($this->apiVersion >= 2) {
             header("Last-Modified-Version: " . $this->libraryVersion);
         }
         // Send notification if library has changed
         if ($this->isWriteMethod()) {
             if ($this->libraryVersion > Zotero_Libraries::getOriginalVersion($this->objectLibraryID)) {
                 Zotero_Notifier::trigger('modify', 'library', $this->objectLibraryID);
             }
         }
     }
     if ($this->responseXML instanceof SimpleXMLElement) {
         if (!$this->responseCode) {
             $updated = (string) $this->responseXML->updated;
             if ($updated) {
                 $updated = strtotime($updated);
                 $ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? $_SERVER['HTTP_IF_MODIFIED_SINCE'] : false;
                 $ifModifiedSince = strtotime($ifModifiedSince);
                 if ($ifModifiedSince >= $updated) {
                     header('HTTP/1.1 304 Not Modified');
                     exit;
                 }
                 $lastModified = substr(date('r', $updated), 0, -5) . "GMT";
                 header("Last-Modified: {$lastModified}");
             }
         }
         $xmlstr = $this->responseXML->asXML();
         // TEMP: Strip control characters
         $xmlstr = Zotero_Utilities::cleanString($xmlstr, true);
         $doc = new DOMDocument('1.0');
         $doc->loadXML($xmlstr);
         $doc->formatOutput = true;
         echo $doc->saveXML();
     }
     $this->logRequestTime();
     self::addHeaders();
     echo ob_get_clean();
     exit;
 }
Esempio n. 11
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;
     }
 }
Esempio n. 12
0
 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();
 }
Esempio n. 13
0
	public function toResponseJSON($requestParams=[], Zotero_Permissions $permissions, $sharedData=null) {
		$t = microtime(true);
		
		if (!$this->loaded['primaryData']) {
			$this->loadPrimaryData();
		}
		if (!$this->loaded['itemData']) {
			$this->loadItemData();
		}
		
		// Uncached stuff or parts of the cache key
		$version = $this->version;
		$parent = $this->getSource();
		$isRegularItem = !$parent && $this->isRegularItem();
		$downloadDetails = $permissions->canAccess($this->libraryID, 'files')
								? Zotero_Storage::getDownloadDetails($this)
								: false;
		if ($isRegularItem) {
			$numChildren = $permissions->canAccess($this->libraryID, 'notes')
								? $this->numChildren()
								: $this->numAttachments();
		}
		$libraryType = Zotero_Libraries::getType($this->libraryID);
		
		// Any query parameters that have an effect on the output
		// need to be added here
		$allowedParams = [
			'include',
			'style',
			'css',
			'linkwrap'
		];
		$cachedParams = Z_Array::filterKeys($requestParams, $allowedParams);
		
		$cacheVersion = 1;
		$cacheKey = "jsonEntry_" . $this->libraryID . "/" . $this->id . "_"
			. md5(
				$version
				. json_encode($cachedParams)
				. ($downloadDetails ? 'hasFile' : '')
				// For groups, include the group WWW URL, which can change
				. ($libraryType == 'group' ? Zotero_URI::getItemURI($this, true) : '')
			)
			. "_" . $requestParams['v']
			// For code-based changes
			. "_" . $cacheVersion
			// For data-based changes
			. (isset(Z_CONFIG::$CACHE_VERSION_RESPONSE_JSON_ITEM)
				? "_" . Z_CONFIG::$CACHE_VERSION_RESPONSE_JSON_ITEM
				: "")
			// If there's bib content, include the bib cache version
			. ((in_array('bib', $requestParams['include'])
					&& isset(Z_CONFIG::$CACHE_VERSION_BIB))
				? "_" . Z_CONFIG::$CACHE_VERSION_BIB
				: "");
		
		$cached = Z_Core::$MC->get($cacheKey);
		if (false && $cached) {
			// Make sure numChildren reflects the current permissions
			if ($isRegularItem) {
				$json = json_decode($cached);
				$json['numChildren'] = $numChildren;
				$cached = json_encode($json);
			}
			
			//StatsD::timing("api.items.itemToResponseJSON.cached", (microtime(true) - $t) * 1000);
			//StatsD::increment("memcached.items.itemToResponseJSON.hit");
			
			// Skip the cache every 10 times for now, to ensure cache sanity
			if (!Z_Core::probability(10)) {
				return $cached;
			}
		}
		
		
		$json = [
			'key' => $this->key,
			'version' => $version,
			'library' => Zotero_Libraries::toJSON($this->libraryID)
		];
		
		$json['links'] = [
			'self' => [
				'href' => Zotero_API::getItemURI($this),
				'type' => 'application/json'
			],
			'alternate' => [
				'href' => Zotero_URI::getItemURI($this, true),
				'type' => 'text/html'
			]
		];
		
		if ($parent) {
			$parentItem = Zotero_Items::get($this->libraryID, $parent);
			$json['links']['up'] = [
				'href' => Zotero_API::getItemURI($parentItem),
				'type' => 'application/json'
			];
		}
		
		// If appropriate permissions and the file is stored in ZFS, get file request link
		if ($downloadDetails) {
			$details = $downloadDetails;
			$type = $this->attachmentMIMEType;
			if ($type) {
				$json['links']['enclosure'] = [
					'type' => $type
				];
			}
			$json['links']['enclosure']['href'] = $details['url'];
			if (!empty($details['filename'])) {
				$json['links']['enclosure']['title'] = $details['filename'];
			}
			if (isset($details['size'])) {
				$json['links']['enclosure']['length'] = $details['size'];
			}
		}
		
		// 'meta'
		$json['meta'] = new stdClass;
		
		if (Zotero_Libraries::getType($this->libraryID) == 'group') {
			$createdByUserID = $this->createdByUserID;
			$lastModifiedByUserID = $this->lastModifiedByUserID;
			
			if ($createdByUserID) {
				$json['meta']->createdByUser = Zotero_Users::toJSON($createdByUserID);
			}
			
			if ($lastModifiedByUserID && $lastModifiedByUserID != $createdByUserID) {
				$json['meta']->lastModifiedByUser = Zotero_Users::toJSON($lastModifiedByUserID);
			}
		}
		
		if ($isRegularItem) {
			$val = $this->getCreatorSummary();
			if ($val !== '') {
				$json['meta']->creatorSummary = $val;
			}
			
			$val = $this->getField('date', true, true, true);
			if ($val !== '') {
				$sqlDate = Zotero_Date::multipartToSQL($val);
				if (substr($sqlDate, 0, 4) !== '0000') {
					$json['meta']->parsedDate = Zotero_Date::sqlToISO8601($sqlDate);
				}
			}
			
			$json['meta']->numChildren = $numChildren;
		}
		
		// 'include'
		$include = $requestParams['include'];
		
		foreach ($include as $type) {
			if ($type == 'html') {
				$json[$type] = trim($this->toHTML());
			}
			else if ($type == 'citation') {
				if (isset($sharedData[$type][$this->libraryID . "/" . $this->key])) {
					$html = $sharedData[$type][$this->libraryID . "/" . $this->key];
				}
				else {
					if ($sharedData !== null) {
						//error_log("Citation not found in sharedData -- retrieving individually");
					}
					$html = Zotero_Cite::getCitationFromCiteServer($this, $requestParams);
				}
				$json[$type] = $html;
			}
			else if ($type == 'bib') {
				if (isset($sharedData[$type][$this->libraryID . "/" . $this->key])) {
					$html = $sharedData[$type][$this->libraryID . "/" . $this->key];
				}
				else {
					if ($sharedData !== null) {
						//error_log("Bibliography not found in sharedData -- retrieving individually");
					}
					$html = Zotero_Cite::getBibliographyFromCitationServer([$this], $requestParams);
					
					// Strip prolog
					$html = preg_replace('/^<\?xml.+\n/', "", $html);
					$html = trim($html);
				}
				$json[$type] = $html;
			}
			else if ($type == 'data') {
				$json[$type] = $this->toJSON(true, $requestParams, true);
			}
			else if ($type == 'csljson') {
				$json[$type] = $this->toCSLItem();
			}
			else if (in_array($type, Zotero_Translate::$exportFormats)) {
				$export = Zotero_Translate::doExport([$this], $type);
				$json[$type] = $export['body'];
				unset($export);
			}
		}
		

		// TEMP
		if ($cached) {
			$uncached = Zotero_Utilities::formatJSON($json);
			if ($cached != $uncached) {
				error_log("Cached JSON item entry does not match");
				error_log("  Cached: " . $cached);
				error_log("Uncached: " . $uncached);
				
				//Z_Core::$MC->set($cacheKey, $uncached, 3600); // 1 hour for now
			}
		}
		else {
			/*Z_Core::$MC->set($cacheKey, $xmlstr, 3600); // 1 hour for now
			StatsD::timing("api.items.itemToAtom.uncached", (microtime(true) - $t) * 1000);
			StatsD::increment("memcached.items.itemToAtom.miss");*/
		}
		
		return $json;
	}
Esempio n. 14
0
 public function toSolrDocument()
 {
     $doc = new SolrInputDocument();
     $uri = Zotero_Solr::getItemURI($this->libraryID, $this->key);
     $doc->addField("uri", $uri);
     // Primary fields
     foreach (Zotero_Items::$primaryFields as $field) {
         switch ($field) {
             case 'itemID':
             case 'numAttachments':
             case 'numNotes':
                 continue 2;
             case 'itemTypeID':
                 $xmlField = 'itemType';
                 $xmlValue = Zotero_ItemTypes::getName($this->{$field});
                 break;
             case 'dateAdded':
             case 'dateModified':
             case 'serverDateModified':
                 $xmlField = $field;
                 $xmlValue = Zotero_Date::sqlToISO8601($this->{$field});
                 break;
             default:
                 $xmlField = $field;
                 $xmlValue = $this->{$field};
         }
         $doc->addField($xmlField, $xmlValue);
     }
     // Title for sorting
     $title = $this->getDisplayTitle(true);
     $title = $title ? $title : '';
     // Strip HTML from note titles
     if ($this->isNote()) {
         // Clean and strip HTML, giving us an HTML-encoded plaintext string
         $title = strip_tags($GLOBALS['HTMLPurifier']->purify($title));
         // Unencode plaintext string
         $title = html_entity_decode($title);
     }
     // Strip some characters
     $sortTitle = preg_replace("/^[\\[\\'\"]*(.*)[\\]\\'\"]*\$/", "\$1", $title);
     if ($sortTitle) {
         $doc->addField('titleSort', $sortTitle);
     }
     // Item data
     $fieldIDs = $this->getUsedFields();
     foreach ($fieldIDs as $fieldID) {
         $val = $this->getField($fieldID);
         if ($val == '') {
             continue;
         }
         $fieldName = Zotero_ItemFields::getName($fieldID);
         switch ($fieldName) {
             // As is
             case 'title':
                 $val = $title;
                 break;
                 // Date fields
             // Date fields
             case 'date':
                 // Add user part as text
                 $doc->addField($fieldName . "_t", Zotero_Date::multipartToStr($val));
                 // Add as proper date, if there is one
                 $sqlDate = Zotero_Date::multipartToSQL($val);
                 if (!$sqlDate || $sqlDate == '0000-00-00') {
                     continue 2;
                 }
                 $fieldName .= "_tdt";
                 $val = Zotero_Date::sqlToISO8601($sqlDate);
                 break;
             case 'accessDate':
                 if (!Zotero_Date::isSQLDateTime($val)) {
                     continue 2;
                 }
                 $fieldName .= "_tdt";
                 $val = Zotero_Date::sqlToISO8601($val);
                 break;
             default:
                 $fieldName .= "_t";
         }
         $doc->addField($fieldName, $val);
     }
     // Deleted item flag
     if ($this->getDeleted()) {
         $doc->addField('deleted', true);
     }
     if ($this->isNote() || $this->isAttachment()) {
         $sourceItemID = $this->getSource();
         if ($sourceItemID) {
             $sourceItem = Zotero_Items::get($this->libraryID, $sourceItemID);
             if (!$sourceItem) {
                 throw new Exception("Source item {$sourceItemID} not found");
             }
             $doc->addField('sourceItem', $sourceItem->key);
         }
     }
     // Group modification info
     $createdByUserID = null;
     $lastModifiedByUserID = null;
     switch (Zotero_Libraries::getType($this->libraryID)) {
         case 'group':
             $createdByUserID = $this->createdByUserID;
             $lastModifiedByUserID = $this->lastModifiedByUserID;
             break;
     }
     if ($createdByUserID) {
         $doc->addField('createdByUserID', $createdByUserID);
     }
     if ($lastModifiedByUserID) {
         $doc->addField('lastModifiedByUserID', $lastModifiedByUserID);
     }
     // Note
     if ($this->isNote()) {
         $doc->addField('note', $this->getNote());
     }
     if ($this->isAttachment()) {
         $doc->addField('linkMode', $this->attachmentLinkMode);
         $doc->addField('mimeType', $this->attachmentMIMEType);
         if ($this->attachmentCharset) {
             $doc->addField('charset', $this->attachmentCharset);
         }
         // TODO: get from a constant
         if ($this->attachmentLinkMode != 3) {
             $doc->addField('path', $this->attachmentPath);
         }
         $note = $this->getNote();
         if ($note) {
             $doc->addField('note', $note);
         }
     }
     // Creators
     $creators = $this->getCreators();
     if ($creators) {
         foreach ($creators as $index => $creator) {
             $c = $creator['ref'];
             $doc->addField('creatorKey', $c->key);
             if ($c->fieldMode == 0) {
                 $doc->addField('creatorFirstName', $c->firstName);
             }
             $doc->addField('creatorLastName', $c->lastName);
             $doc->addField('creatorType', Zotero_CreatorTypes::getName($creator['creatorTypeID']));
             $doc->addField('creatorIndex', $index);
         }
     }
     // Tags
     $tags = $this->getTags();
     if ($tags) {
         foreach ($tags as $tag) {
             $doc->addField('tagKey', $tag->key);
             $doc->addField('tag', $tag->name);
             $doc->addField('tagType', $tag->type);
         }
     }
     // Related items
     /*$related = $this->relatedItems;
     		if ($related) {
     			$related = Zotero_Items::get($this->libraryID, $related);
     			$keys = array();
     			foreach ($related as $item) {
     				$doc->addField('relatedItem', $item->key);
     			}
     		}*/
     return $doc;
 }
Esempio n. 15
0
 private static function validateJSONItem($json, $libraryID, Zotero_Item $item = null, $isChild, $requestParams, $partialUpdate = false)
 {
     $isNew = !$item || !$item->version;
     if (!is_object($json)) {
         throw new Exception("Invalid item object (found " . gettype($json) . " '" . $json . "')", Z_ERROR_INVALID_INPUT);
     }
     if (isset($json->items) && is_array($json->items)) {
         throw new Exception("An 'items' array is not valid for single-item updates", Z_ERROR_INVALID_INPUT);
     }
     $apiVersion = $requestParams['v'];
     if ($partialUpdate) {
         $requiredProps = [];
     } else {
         if (isset($json->itemType) && $json->itemType == "attachment") {
             $requiredProps = array('linkMode', 'tags');
         } else {
             if (isset($json->itemType) && $json->itemType == "attachment") {
                 $requiredProps = array('tags');
             } else {
                 if ($isNew) {
                     $requiredProps = array('itemType');
                 } else {
                     if ($apiVersion < 2) {
                         $requiredProps = array('itemType', 'tags');
                     } else {
                         $requiredProps = array('itemType', 'tags', 'relations');
                         if (!$isChild) {
                             $requiredProps[] = 'collections';
                         }
                     }
                 }
             }
         }
     }
     foreach ($requiredProps as $prop) {
         if (!isset($json->{$prop})) {
             throw new Exception("'{$prop}' property not provided", Z_ERROR_INVALID_INPUT);
         }
     }
     // For partial updates where item type isn't provided, use the existing item type
     if (!isset($json->itemType) && $partialUpdate) {
         $itemType = Zotero_ItemTypes::getName($item->itemTypeID);
     } else {
         $itemType = $json->itemType;
     }
     foreach ($json as $key => $val) {
         switch ($key) {
             // Handled by Zotero_API::checkJSONObjectVersion()
             case 'key':
             case 'version':
                 if ($apiVersion < 3) {
                     throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'itemKey':
             case 'itemVersion':
                 if ($apiVersion != 2) {
                     throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'parentItem':
                 if ($apiVersion < 2) {
                     throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 if (!Zotero_ID::isValidKey($val) && $val !== false) {
                     throw new Exception("'{$key}' must be a valid item key", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'itemType':
                 if (!is_string($val)) {
                     throw new Exception("'itemType' must be a string", Z_ERROR_INVALID_INPUT);
                 }
                 if ($isChild || !empty($json->parentItem)) {
                     switch ($val) {
                         case 'note':
                         case 'attachment':
                             break;
                         default:
                             throw new Exception("Child item must be note or attachment", Z_ERROR_INVALID_INPUT);
                     }
                 } else {
                     if ($val == 'attachment' && (!$item || !$item->getSource())) {
                         if ($json->linkMode == 'linked_url' || $json->linkMode == 'imported_url' && (empty($json->contentType) || $json->contentType != 'application/pdf')) {
                             throw new Exception("Only file attachments and PDFs can be top-level items", Z_ERROR_INVALID_INPUT);
                         }
                     }
                 }
                 if (!Zotero_ItemTypes::getID($val)) {
                     throw new Exception("'{$val}' is not a valid itemType", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'tags':
                 if (!is_array($val)) {
                     throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT);
                 }
                 foreach ($val as $tag) {
                     $empty = true;
                     if (is_string($tag)) {
                         if ($tag === "") {
                             throw new Exception("Tag cannot be empty", Z_ERROR_INVALID_INPUT);
                         }
                         continue;
                     }
                     if (!is_object($tag)) {
                         throw new Exception("Tag must be an object", Z_ERROR_INVALID_INPUT);
                     }
                     foreach ($tag as $k => $v) {
                         switch ($k) {
                             case 'tag':
                                 if (!is_scalar($v)) {
                                     throw new Exception("Invalid tag name", Z_ERROR_INVALID_INPUT);
                                 }
                                 if ($v === "") {
                                     throw new Exception("Tag cannot be empty", Z_ERROR_INVALID_INPUT);
                                 }
                                 break;
                             case 'type':
                                 if (!is_numeric($v)) {
                                     throw new Exception("Invalid tag type '{$v}'", Z_ERROR_INVALID_INPUT);
                                 }
                                 break;
                             default:
                                 throw new Exception("Invalid tag property '{$k}'", Z_ERROR_INVALID_INPUT);
                         }
                         $empty = false;
                     }
                     if ($empty) {
                         throw new Exception("Tag object is empty", Z_ERROR_INVALID_INPUT);
                     }
                 }
                 break;
             case 'collections':
                 if (!is_array($val)) {
                     throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT);
                 }
                 if ($isChild && $val) {
                     throw new Exception("Child items cannot be assigned to collections", Z_ERROR_INVALID_INPUT);
                 }
                 foreach ($val as $k) {
                     if (!Zotero_ID::isValidKey($k)) {
                         throw new Exception("'{$k}' is not a valid collection key", Z_ERROR_INVALID_INPUT);
                     }
                 }
                 break;
             case 'relations':
                 if ($apiVersion < 2) {
                     throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 if (!is_object($val) && !(is_array($val) && empty($val))) {
                     throw new Exception("'{$key}' property must be an object", Z_ERROR_INVALID_INPUT);
                 }
                 foreach ($val as $predicate => $object) {
                     switch ($predicate) {
                         case 'owl:sameAs':
                         case 'dc:replaces':
                         case 'dc:relation':
                             break;
                         default:
                             throw new Exception("Unsupported predicate '{$predicate}'", Z_ERROR_INVALID_INPUT);
                     }
                     $arr = is_string($object) ? [$object] : $object;
                     foreach ($arr as $uri) {
                         if (!preg_match('/^http:\\/\\/zotero.org\\/(users|groups)\\/[0-9]+\\/(publications\\/)?items\\/[A-Z0-9]{8}$/', $uri)) {
                             throw new Exception("'{$key}' values currently must be Zotero item URIs", Z_ERROR_INVALID_INPUT);
                         }
                     }
                 }
                 break;
             case 'creators':
                 if (!is_array($val)) {
                     throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT);
                 }
                 foreach ($val as $creator) {
                     $empty = true;
                     if (!isset($creator->creatorType)) {
                         throw new Exception("creator object must contain 'creatorType'", Z_ERROR_INVALID_INPUT);
                     }
                     if ((!isset($creator->name) || trim($creator->name) == "") && (!isset($creator->firstName) || trim($creator->firstName) == "") && (!isset($creator->lastName) || trim($creator->lastName) == "")) {
                         // On item creation, ignore single nameless creator,
                         // because that's in the item template that the API returns
                         if (sizeOf($val) == 1 && $isNew) {
                             continue;
                         } else {
                             throw new Exception("creator object must contain 'firstName'/'lastName' or 'name'", Z_ERROR_INVALID_INPUT);
                         }
                     }
                     foreach ($creator as $k => $v) {
                         switch ($k) {
                             case 'creatorType':
                                 $creatorTypeID = Zotero_CreatorTypes::getID($v);
                                 if (!$creatorTypeID) {
                                     throw new Exception("'{$v}' is not a valid creator type", Z_ERROR_INVALID_INPUT);
                                 }
                                 $itemTypeID = Zotero_ItemTypes::getID($itemType);
                                 if (!Zotero_CreatorTypes::isValidForItemType($creatorTypeID, $itemTypeID)) {
                                     // Allow 'author' in all item types, but reject other invalid creator types
                                     if ($creatorTypeID != Zotero_CreatorTypes::getID('author')) {
                                         throw new Exception("'{$v}' is not a valid creator type for item type '{$itemType}'", Z_ERROR_INVALID_INPUT);
                                     }
                                 }
                                 break;
                             case 'firstName':
                                 if (!isset($creator->lastName)) {
                                     throw new Exception("'lastName' creator field must be set if 'firstName' is set", Z_ERROR_INVALID_INPUT);
                                 }
                                 if (isset($creator->name)) {
                                     throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT);
                                 }
                                 break;
                             case 'lastName':
                                 if (!isset($creator->firstName)) {
                                     throw new Exception("'firstName' creator field must be set if 'lastName' is set", Z_ERROR_INVALID_INPUT);
                                 }
                                 if (isset($creator->name)) {
                                     throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT);
                                 }
                                 break;
                             case 'name':
                                 if (isset($creator->firstName)) {
                                     throw new Exception("'firstName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT);
                                 }
                                 if (isset($creator->lastName)) {
                                     throw new Exception("'lastName' and 'name' creator fields are mutually exclusive", Z_ERROR_INVALID_INPUT);
                                 }
                                 break;
                             default:
                                 throw new Exception("Invalid creator property '{$k}'", Z_ERROR_INVALID_INPUT);
                         }
                         $empty = false;
                     }
                     if ($empty) {
                         throw new Exception("Creator object is empty", Z_ERROR_INVALID_INPUT);
                     }
                 }
                 break;
             case 'note':
                 switch ($itemType) {
                     case 'note':
                     case 'attachment':
                         break;
                     default:
                         throw new Exception("'note' property is valid only for note and attachment items", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'attachments':
             case 'notes':
                 if ($apiVersion > 1) {
                     throw new Exception("'{$key}' property is no longer supported", Z_ERROR_INVALID_INPUT);
                 }
                 if (!$isNew) {
                     throw new Exception("'{$key}' property is valid only for new items", Z_ERROR_INVALID_INPUT);
                 }
                 if (!is_array($val)) {
                     throw new Exception("'{$key}' property must be an array", Z_ERROR_INVALID_INPUT);
                 }
                 foreach ($val as $child) {
                     // Check child item type ('attachment' or 'note')
                     $t = substr($key, 0, -1);
                     if (isset($child->itemType) && $child->itemType != $t) {
                         throw new Exception("Child {$t} must be of itemType '{$t}'", Z_ERROR_INVALID_INPUT);
                     }
                     if ($key == 'note') {
                         if (!isset($child->note)) {
                             throw new Exception("'note' property not provided for child note", Z_ERROR_INVALID_INPUT);
                         }
                     }
                 }
                 break;
             case 'deleted':
                 break;
                 // Attachment properties
             // Attachment properties
             case 'linkMode':
                 try {
                     $linkMode = Zotero_Attachments::linkModeNameToNumber($val, true);
                 } catch (Exception $e) {
                     throw new Exception("'{$val}' is not a valid linkMode", Z_ERROR_INVALID_INPUT);
                 }
                 // Don't allow changing of linkMode
                 if (!$isNew && $linkMode != $item->attachmentLinkMode) {
                     throw new Exception("Cannot change attachment linkMode", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'contentType':
             case 'charset':
             case 'filename':
             case 'md5':
             case 'mtime':
                 if ($itemType != 'attachment') {
                     throw new Exception("'{$key}' is valid only for attachment items", Z_ERROR_INVALID_INPUT);
                 }
                 switch ($key) {
                     case 'filename':
                     case 'md5':
                     case 'mtime':
                         $lm = isset($json->linkMode) ? $json->linkMode : Zotero_Attachments::linkModeNumberToName($item->attachmentLinkMode);
                         if (strpos(strtolower($lm), 'imported_') !== 0) {
                             throw new Exception("'{$key}' is valid only for imported attachment items", Z_ERROR_INVALID_INPUT);
                         }
                         break;
                 }
                 switch ($key) {
                     case 'contentType':
                     case 'charset':
                     case 'filename':
                         $propName = 'attachment' . ucwords($key);
                         break;
                     case 'md5':
                         $propName = 'attachmentStorageHash';
                         break;
                     case 'mtime':
                         $propName = 'attachmentStorageModTime';
                         break;
                 }
                 if (Zotero_Libraries::getType($libraryID) == 'group') {
                     if ($item && $item->{$propName} !== $val || !$item && $val !== null && $val !== "") {
                         throw new Exception("Cannot change '{$key}' directly in group library", Z_ERROR_INVALID_INPUT);
                     }
                 } else {
                     if ($key == 'md5') {
                         if ($val && !preg_match("/^[a-f0-9]{32}\$/", $val)) {
                             throw new Exception("'{$val}' is not a valid MD5 hash", Z_ERROR_INVALID_INPUT);
                         }
                     }
                 }
                 break;
             case 'accessDate':
                 if ($apiVersion >= 3 && $val !== '' && $val != 'CURRENT_TIMESTAMP' && !Zotero_Date::isSQLDate($val) && !Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) {
                     throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD[ hh-mm-dd]' format or 'CURRENT_TIMESTAMP' ({$val})", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             case 'dateAdded':
                 if (!Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) {
                     throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD hh-mm-dd' format", Z_ERROR_INVALID_INPUT);
                 }
                 if (!$isNew) {
                     // Convert ISO date to SQL date for equality comparison
                     if (Zotero_Date::isISO8601($val)) {
                         $val = Zotero_Date::iso8601ToSQL($val);
                     }
                     if ($val != $item->{$key}) {
                         throw new Exception("'{$key}' cannot be modified for existing items", Z_ERROR_INVALID_INPUT);
                     }
                 }
                 break;
             case 'dateModified':
                 if (!Zotero_Date::isSQLDateTime($val) && !Zotero_Date::isISO8601($val)) {
                     throw new Exception("'{$key}' must be in ISO 8601 or UTC 'YYYY-MM-DD hh-mm-dd' format ({$val})", Z_ERROR_INVALID_INPUT);
                 }
                 break;
             default:
                 if (!Zotero_ItemFields::getID($key)) {
                     throw new Exception("Invalid property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 if (is_array($val)) {
                     throw new Exception("Unexpected array for property '{$key}'", Z_ERROR_INVALID_INPUT);
                 }
                 break;
         }
     }
     // Publications libraries have additional restrictions
     if (Zotero_Libraries::getType($libraryID) == 'publications') {
         Zotero_Publications::validateJSONItem($json);
     }
 }
Esempio n. 16
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;
     }
 }
Esempio n. 17
0
 public static function toJSON($libraryID)
 {
     // TODO: cache
     $libraryType = Zotero_Libraries::getType($libraryID);
     if ($libraryType == 'user') {
         $objectUserID = Zotero_Users::getUserIDFromLibraryID($libraryID);
         $json = ['type' => $libraryType, 'id' => $objectUserID, 'name' => self::getName($libraryID), 'links' => ['alternate' => ['href' => Zotero_URI::getUserURI($objectUserID, true), 'type' => 'text/html']]];
     } else {
         if ($libraryType == 'publications') {
             $objectUserID = Zotero_Users::getUserIDFromLibraryID($libraryID);
             $json = ['type' => $libraryType, 'id' => $objectUserID, 'name' => self::getName($libraryID), 'links' => ['alternate' => ['href' => Zotero_URI::getUserURI($objectUserID, true) . "/publications", 'type' => 'text/html']]];
         } else {
             if ($libraryType == 'group') {
                 $objectGroupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
                 $group = Zotero_Groups::get($objectGroupID);
                 $json = ['type' => $libraryType, 'id' => $objectGroupID, 'name' => self::getName($libraryID), 'links' => ['alternate' => ['href' => Zotero_URI::getGroupURI($group, true), 'type' => 'text/html']]];
             } else {
                 throw new Exception("Invalid library type '{$libraryType}'");
             }
         }
     }
     return $json;
 }
Esempio n. 18
0
 /**
  * Generate a SimpleXMLElement Atom object for the search
  *
  * @param array $queryParams
  * @return SimpleXMLElement
  */
 public function toAtom($queryParams)
 {
     if (!$this->loaded) {
         $this->load();
     }
     // TEMP: multi-format support
     if (!empty($queryParams['content'])) {
         $content = $queryParams['content'];
     } else {
         $content = array('none');
     }
     $content = $content[0];
     $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>' . '<entry xmlns="' . Zotero_Atom::$nsAtom . '" xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"/>');
     $xml->title = $this->name ? $this->name : '[Untitled]';
     $author = $xml->addChild('author');
     // TODO: group item creator
     $author->name = Zotero_Libraries::getName($this->libraryID);
     $author->uri = Zotero_URI::getLibraryURI($this->libraryID, true);
     $xml->id = Zotero_URI::getSearchURI($this);
     $xml->published = Zotero_Date::sqlToISO8601($this->dateAdded);
     $xml->updated = Zotero_Date::sqlToISO8601($this->dateModified);
     $link = $xml->addChild("link");
     $link['rel'] = "self";
     $link['type'] = "application/atom+xml";
     $link['href'] = Zotero_API::getSearchURI($this);
     $xml->addChild('zapi:key', $this->key, Zotero_Atom::$nsZoteroAPI);
     $xml->addChild('zapi:version', $this->version, Zotero_Atom::$nsZoteroAPI);
     if ($content == 'json') {
         $xml->content['type'] = 'application/json';
         $xml->content = Zotero_Utilities::formatJSON($this->toJSON($queryParams));
     }
     return $xml;
 }
Esempio n. 19
0
 public static function isEditable($obj)
 {
     $type = static::field('object');
     // Only enforce for sync controller for now
     if (empty($GLOBALS['controller']) || !$GLOBALS['controller'] instanceof SyncController) {
         return true;
     }
     // Make sure user has access privileges to delete
     $userID = $GLOBALS['controller']->userID;
     if (!$userID) {
         return true;
     }
     $objectLibraryID = $obj->libraryID;
     $libraryType = Zotero_Libraries::getType($objectLibraryID);
     switch ($libraryType) {
         case 'user':
             if (!empty($GLOBALS['controller']->userLibraryID)) {
                 $userLibraryID = $GLOBALS['controller']->userLibraryID;
             } else {
                 $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
             }
             if ($objectLibraryID != $userLibraryID) {
                 return false;
             }
             return true;
         case 'group':
             $groupID = Zotero_Groups::getGroupIDFromLibraryID($objectLibraryID);
             $group = Zotero_Groups::get($groupID);
             if (!$group->hasUser($userID) || !$group->userCanEdit($userID)) {
                 return false;
             }
             if ($type == 'item' && $obj->isImportedAttachment() && !$group->userCanEditFiles($userID)) {
                 return false;
             }
             return true;
         default:
             throw new Exception("Unsupported library type '{$libraryType}'");
     }
 }
Esempio n. 20
0
 public function toResponseJSON($requestParams = [])
 {
     $t = microtime(true);
     // Child collections and items can't be cached (easily)
     $numCollections = $this->numCollections();
     $numItems = $this->numItems();
     if (!$requestParams['uncached']) {
         $cacheKey = $this->getCacheKey($requestParams);
         $cached = Z_Core::$MC->get($cacheKey);
         if ($cached) {
             Z_Core::debug("Using cached JSON for {$this->libraryKey}");
             $cached['meta']->numCollections = $numCollections;
             $cached['meta']->numItems = $numItems;
             StatsD::timing("api.collections.toResponseJSON.cached", (microtime(true) - $t) * 1000);
             StatsD::increment("memcached.collections.toResponseJSON.hit");
             return $cached;
         }
     }
     $json = ['key' => $this->key, 'version' => $this->version, 'library' => Zotero_Libraries::toJSON($this->libraryID)];
     // 'links'
     $json['links'] = ['self' => ['href' => Zotero_API::getCollectionURI($this), 'type' => 'application/json'], 'alternate' => ['href' => Zotero_URI::getCollectionURI($this, true), 'type' => 'text/html']];
     $parentID = $this->getParentID();
     if ($parentID) {
         $parentCol = Zotero_Collections::get($this->libraryID, $parentID);
         $json['links']['up'] = ['href' => Zotero_API::getCollectionURI($parentCol), 'type' => "application/atom+xml"];
     }
     // 'meta'
     $json['meta'] = new stdClass();
     $json['meta']->numCollections = $numCollections;
     $json['meta']->numItems = $numItems;
     // 'include'
     $include = $requestParams['include'];
     foreach ($include as $type) {
         if ($type == 'data') {
             $json[$type] = $this->toJSON($requestParams);
         }
     }
     if (!$requestParams['uncached']) {
         Z_Core::$MC->set($cacheKey, $json);
         StatsD::timing("api.collections.toResponseJSON.uncached", (microtime(true) - $t) * 1000);
         StatsD::increment("memcached.collections.toResponseJSON.miss");
     }
     return $json;
 }
Esempio n. 21
0
 public function collections()
 {
     // 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);
     }
     $collectionIDs = array();
     $collectionKeys = array();
     $results = array();
     // Single collection
     if ($this->singleObject) {
         $this->allowMethods(['HEAD', 'GET', 'PUT', 'PATCH', 'DELETE']);
         if (!Zotero_ID::isValidKey($this->objectKey)) {
             $this->e404();
         }
         $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->objectKey);
         if ($this->isWriteMethod()) {
             $collection = $this->handleObjectWrite('collection', $collection ? $collection : null);
             $this->queryParams['content'] = ['json'];
         }
         if (!$collection) {
             $this->e404("Collection not found");
         }
         $this->libraryVersion = $collection->version;
         if ($this->method == 'HEAD') {
             $this->end();
         }
         switch ($this->queryParams['format']) {
             case 'atom':
                 $this->responseXML = Zotero_Collections::convertCollectionToAtom($collection, $this->queryParams);
                 break;
             case 'json':
                 $json = $collection->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);
         if ($this->scopeObject) {
             $this->allowMethods(array('GET'));
             switch ($this->scopeObject) {
                 case 'collections':
                     $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                     if (!$collection) {
                         $this->e404("Collection not found");
                     }
                     $title = "Child Collections of ‘{$collection->name}'’";
                     $collectionIDs = $collection->getChildCollections();
                     break;
                 default:
                     throw new Exception("Invalid collections scope object '{$this->scopeObject}'");
             }
         } else {
             // Top-level items
             if ($this->subset == 'top') {
                 $this->allowMethods(array('GET'));
                 $title = "Top-Level Collections";
                 $results = Zotero_Collections::search($this->objectLibraryID, true, $this->queryParams);
             } else {
                 // Create a collection
                 if ($this->method == 'POST') {
                     $this->queryParams['format'] = 'writereport';
                     $obj = $this->jsonDecode($this->body);
                     $results = Zotero_Collections::updateMultipleFromJSON($obj, $this->queryParams, $this->objectLibraryID, $this->userID, $this->permissions, $libraryTimestampChecked ? 0 : 1, null);
                     if ($cacheKey = $this->getWriteTokenCacheKey()) {
                         Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime);
                     }
                     if ($this->apiVersion < 2) {
                         $uri = Zotero_API::getCollectionsURI($this->objectLibraryID);
                         $keys = array_merge(get_object_vars($results['success']), get_object_vars($results['unchanged']));
                         $queryString = "collectionKey=" . urlencode(implode(",", $keys)) . "&format=atom&content=json&order=collectionKeyList&sort=asc";
                         if ($this->apiKey) {
                             $queryString .= "&key=" . $this->apiKey;
                         }
                         $uri .= "?" . $queryString;
                         $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, true, $this->apiVersion);
                         $title = "Collections";
                         $results = Zotero_Collections::search($this->objectLibraryID, false, $this->queryParams);
                     }
                 } else {
                     if ($this->method == 'DELETE') {
                         Zotero_DB::beginTransaction();
                         foreach ($this->queryParams['collectionKey'] as $collectionKey) {
                             Zotero_Collections::delete($this->objectLibraryID, $collectionKey);
                         }
                         Zotero_DB::commit();
                         $this->e204();
                     } else {
                         $title = "Collections";
                         $results = Zotero_Collections::search($this->objectLibraryID, false, $this->queryParams);
                     }
                 }
             }
         }
         if ($collectionIDs) {
             $this->queryParams['collectionIDs'] = $collectionIDs;
             $results = Zotero_Collections::search($this->objectLibraryID, false, $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();
 }
Esempio n. 22
0
 public static function notify($event, $type, $ids, $extraData)
 {
     if (!isset(Z_CONFIG::$SNS_TOPIC_STREAM_EVENTS)) {
         error_log('WARNING: Z_CONFIG::$SNS_TOPIC_STREAM_EVENTS not set ' . '-- skipping stream notifications');
         return;
     }
     if ($type == "library") {
         switch ($event) {
             case "modify":
                 $event = "topicUpdated";
                 break;
             case "delete":
                 $event = "topicDeleted";
                 break;
             default:
                 return;
         }
         $entries = [];
         foreach ($ids as $id) {
             $libraryID = $id;
             // For most libraries, get topic from URI
             if ($event != 'topicDeleted') {
                 // Convert 'http://zotero.org/users/...' to '/users/...'
                 $topic = str_replace(Zotero_URI::getBaseURI(), "/", Zotero_URI::getLibraryURI($libraryID));
             } else {
                 $topic = '/' . Zotero_Libraries::getType($libraryID) . "s/" . Zotero_Libraries::getLibraryTypeID($libraryID);
             }
             $message = ["event" => $event, "topic" => $topic];
             if (!empty($extraData[$id])) {
                 foreach ($extraData[$id] as $key => $val) {
                     $message[$key] = $val;
                 }
             }
             foreach (self::$messageReceivers as $receiver) {
                 $receiver(Z_CONFIG::$SNS_TOPIC_STREAM_EVENTS, json_encode($message, JSON_UNESCAPED_SLASHES));
             }
         }
     } else {
         if ($type == "apikey-library") {
             switch ($event) {
                 case "add":
                     $event = "topicAdded";
                     break;
                 case "remove":
                     $event = "topicRemoved";
                     break;
                 default:
                     return;
             }
             $entries = [];
             foreach ($ids as $id) {
                 list($apiKey, $libraryID) = explode("-", $id);
                 // Get topic from URI
                 $topic = str_replace(Zotero_URI::getBaseURI(), "/", Zotero_URI::getLibraryURI($libraryID));
                 $message = ["event" => $event, "apiKey" => $apiKey, "topic" => $topic];
                 if (!empty($extraData[$id])) {
                     foreach ($extraData[$id] as $key => $val) {
                         $message[$key] = $val;
                     }
                 }
                 self::send($message);
             }
         }
     }
 }
Esempio n. 23
0
 /**
  * Converts a Zotero_Tag object to a SimpleXMLElement Atom object
  *
  * @param	object				$tag		Zotero_Tag object
  * @param	string				$content
  * @return	SimpleXMLElement					Tag data as SimpleXML element
  */
 public function toAtom($content = array('none'), $apiVersion = null, $fixedValues = null)
 {
     // TEMP: multi-format support
     $content = $content[0];
     $xml = new SimpleXMLElement('<entry xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '" ' . 'xmlns:zxfer="' . Zotero_Atom::$nsZoteroTransfer . '"/>');
     $xml->title = $this->name;
     $author = $xml->addChild('author');
     $author->name = Zotero_Libraries::getName($this->libraryID);
     $author->uri = Zotero_URI::getLibraryURI($this->libraryID);
     $xml->id = Zotero_URI::getTagURI($this);
     $xml->published = Zotero_Date::sqlToISO8601($this->dateAdded);
     $xml->updated = Zotero_Date::sqlToISO8601($this->dateModified);
     $link = $xml->addChild("link");
     $link['rel'] = "self";
     $link['type'] = "application/atom+xml";
     $link['href'] = Zotero_Atom::getTagURI($this);
     $link = $xml->addChild('link');
     $link['rel'] = 'alternate';
     $link['type'] = 'text/html';
     $link['href'] = Zotero_URI::getTagURI($this);
     // Count user's linked items
     if (isset($fixedValues['numItems'])) {
         $numItems = $fixedValues['numItems'];
     } else {
         $itemIDs = $this->getLinkedItems();
         $numItems = sizeOf($itemIDs);
     }
     $xml->addChild('zapi:numItems', $numItems, Zotero_Atom::$nsZoteroAPI);
     if ($content == 'html') {
         $xml->content['type'] = 'xhtml';
         //$fullXML = Zotero_Tags::convertTagToXML($tag);
         $fullStr = "<div/>";
         $fullXML = new SimpleXMLElement($fullStr);
         $fullXML->addAttribute("xmlns", Zotero_Atom::$nsXHTML);
         $fNode = dom_import_simplexml($xml->content);
         $subNode = dom_import_simplexml($fullXML);
         $importedNode = $fNode->ownerDocument->importNode($subNode, true);
         $fNode->appendChild($importedNode);
         //$arr = $tag->serialize();
         //require_once("views/zotero/tags.php")
     } else {
         if ($content == 'full') {
             $xml->content['type'] = 'application/xml';
             $fullXML = $this->toXML();
             $fullXML->addAttribute("xmlns", Zotero_Atom::$nsZoteroTransfer);
             $fNode = dom_import_simplexml($xml->content);
             $subNode = dom_import_simplexml($fullXML);
             $importedNode = $fNode->ownerDocument->importNode($subNode, true);
             $fNode->appendChild($importedNode);
         }
     }
     return $xml;
 }
Esempio n. 24
0
 public static function logUpload($uploadUserID, $item, $key, $ipAddress)
 {
     $libraryID = $item->libraryID;
     $ownerUserID = Zotero_Libraries::getOwner($libraryID);
     $info = self::getUploadInfo($key);
     if (!$info) {
         throw new Exception("Upload key '{$key}' not found in queue");
     }
     $info = self::getLocalFileItemInfo($item);
     $storageFileID = $info['storageFileID'];
     $filename = $info['filename'];
     $size = $info['size'];
     $sql = "DELETE FROM storageUploadQueue WHERE uploadKey=?";
     Zotero_DB::query($sql, $key);
     $sql = "INSERT INTO storageUploadLog\n\t\t\t\t(ownerUserID, uploadUserID, ipAddress, storageFileID, filename, size)\n\t\t\t\tVALUES (?, ?, INET_ATON(?), ?, ?, ?)";
     Zotero_DB::query($sql, array($ownerUserID, $uploadUserID, $ipAddress, $storageFileID, $filename, $size));
 }
Esempio n. 25
0
 /**
  * @param	SimpleXMLElement	$xml		Data necessary for delete as SimpleXML element
  * @return	void
  */
 public static function deleteFromXML(SimpleXMLElement $xml, $userID)
 {
     $parents = array();
     foreach ($xml->children() as $obj) {
         $libraryID = (int) $obj['libraryID'];
         $key = (string) $obj['key'];
         if ($userID && !Zotero_Libraries::userCanEdit($libraryID, $userID)) {
             throw new Exception("Cannot edit " . self::$objectType . " in library {$libraryID}", Z_ERROR_LIBRARY_ACCESS_DENIED);
         }
         if ($obj->getName() == 'item') {
             $item = Zotero_Items::getByLibraryAndKey($libraryID, $key);
             if (!$item) {
                 continue;
             }
             if (!$item->getSource()) {
                 $parents[] = array('libraryID' => $libraryID, 'key' => $key);
                 continue;
             }
         }
         self::delete($libraryID, $key);
     }
     foreach ($parents as $obj) {
         self::delete($obj['libraryID'], $obj['key']);
     }
 }
Esempio n. 26
0
 public static function indexFromXML(DOMElement $xml, $userID)
 {
     if ($xml->textContent === "") {
         error_log("Skipping empty full-text content for item " . $xml->getAttribute('libraryID') . "/" . $xml->getAttribute('key'));
         return;
     }
     $item = Zotero_Items::getByLibraryAndKey($xml->getAttribute('libraryID'), $xml->getAttribute('key'));
     if (!$item) {
         error_log("Item " . $xml->getAttribute('libraryID') . "/" . $xml->getAttribute('key') . " not found during full-text indexing");
         return;
     }
     if (!Zotero_Libraries::userCanEdit($item->libraryID, $userID)) {
         error_log("Skipping full-text content from user {$userID} for uneditable item " . $xml->getAttribute('libraryID') . "/" . $xml->getAttribute('key'));
         return;
     }
     $stats = array();
     foreach (self::$metadata as $prop) {
         $val = $xml->getAttribute($prop);
         $stats[$prop] = $val;
     }
     self::indexItem($item, $xml->textContent, $stats);
 }
Esempio n. 27
0
 public function itemContent()
 {
     $this->allowMethods(array('GET', 'PUT'));
     // Check for general library access
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     if (!$this->singleObject) {
         $this->e404();
     }
     if ($this->isWriteMethod()) {
         // Check for library write access
         if (!$this->permissions->canWrite($this->objectLibraryID)) {
             $this->e403("Write access denied");
         }
         Zotero_Libraries::updateVersionAndTimestamp($this->objectLibraryID);
     }
     $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectKey);
     if (!$item) {
         $this->e404();
     }
     // If no access to the note, don't show that it exists
     if ($item->isNote() && !$this->permissions->canAccess($this->objectLibraryID, 'notes')) {
         $this->e404();
     }
     if (!$item->isAttachment() || Zotero_Attachments::linkModeNumberToName($item->attachmentLinkMode) == 'LINKED_URL') {
         $this->e404();
     }
     if ($this->isWriteMethod()) {
         $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
         if ($this->method == 'PUT') {
             $this->requireContentType("application/json");
             $json = json_decode($this->body, true);
             if (!$json) {
                 $this->e400("PUT data is not valid JSON");
             }
             $stats = [];
             foreach (Zotero_FullText::$metadata as $prop) {
                 if (isset($json[$prop])) {
                     $stats[$prop] = $json[$prop];
                 }
             }
             Zotero_FullText::indexItem($item, $json['content'], $stats);
             $this->e204();
         } else {
             $this->e405();
         }
     }
     $data = Zotero_FullText::getItemData($item->libraryID, $item->key);
     if (!$data) {
         $this->e404();
     }
     $this->libraryVersion = $data['version'];
     $json = ["content" => $data['content']];
     foreach (Zotero_FullText::$metadata as $prop) {
         if (!empty($data[$prop])) {
             $json[$prop] = $data[$prop];
         }
     }
     echo Zotero_Utilities::formatJSON($json);
     $this->end();
 }
Esempio n. 28
0
 public function itemToAtom($itemID)
 {
     if (!is_int($itemID)) {
         throw new Exception("itemID must be an integer (was " . gettype($itemID) . ")");
     }
     if (!$this->loaded) {
         $this->load();
     }
     //$groupUserData = $this->getUserData($itemID);
     $item = Zotero_Items::get($this->libraryID, $itemID);
     if (!$item) {
         throw new Exception("Item {$itemID} doesn't exist");
     }
     $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>' . '<entry xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '" ' . 'xmlns:xfer="' . Zotero_Atom::$nsZoteroTransfer . '"/>');
     $title = $item->getDisplayTitle(true);
     $title = $title ? $title : '[Untitled]';
     // Strip HTML from note titles
     if ($item->isNote()) {
         // Clean and strip HTML, giving us an HTML-encoded plaintext string
         $title = strip_tags(Zotero_Notes::sanitize($title));
         // Unencode plaintext string
         $title = html_entity_decode($title);
     }
     $xml->title = $title;
     $author = $xml->addChild('author');
     $author->name = Zotero_Libraries::getName($item->libraryID);
     $author->uri = Zotero_URI::getLibraryURI($item->libraryID);
     $xml->id = Zotero_URI::getItemURI($item);
     $xml->published = Zotero_Date::sqlToISO8601($item->dateAdded);
     $xml->updated = Zotero_Date::sqlToISO8601($item->dateModified);
     $link = $xml->addChild("link");
     $link['rel'] = "self";
     $link['type'] = "application/atom+xml";
     $link['href'] = Zotero_API::getItemURI($item);
     $link = $xml->addChild('link');
     $link['rel'] = 'alternate';
     $link['type'] = 'text/html';
     $link['href'] = Zotero_URI::getItemURI($item, true);
     $xml->content['type'] = 'application/xml';
     $itemXML = new SimpleXMLElement('<item xmlns="' . Zotero_Atom::$nsZoteroTransfer . '"/>');
     // This method of adding the element seems to be necessary to get the
     // namespace prefix to show up
     $fNode = dom_import_simplexml($xml->content);
     $subNode = dom_import_simplexml($itemXML);
     $importedNode = $fNode->ownerDocument->importNode($subNode, true);
     $fNode->appendChild($importedNode);
     $xml->content->item['id'] = $itemID;
     return $xml;
 }
Esempio n. 29
0
 /**
  * @param object $object Zotero object (Zotero_Item, Zotero_Collection, Zotero_Search, Zotero_Setting)
  * @param object $json JSON object to check
  * @param array $requestParams
  * @param int $requireVersion If 0, don't require; if 1, require if there's
  *                            an object key property in the JSON; if 2,
  *                            always require
  */
 public static function checkJSONObjectVersion($object, $json, $requestParams, $requireVersion)
 {
     $objectType = Zotero_Utilities::getObjectTypeFromObject($object);
     if (!in_array($objectType, array('item', 'collection', 'search', 'setting'))) {
         throw new Exception("Invalid object type");
     }
     $oldKeyProp = $objectType . "Key";
     $oldVersionProp = $objectType == 'setting' ? 'version' : $objectType . "Version";
     $newKeyProp = 'key';
     $newVersionProp = 'version';
     if ($requestParams['v'] >= 3) {
         $keyProp = $newKeyProp;
         $versionProp = $newVersionProp;
         // Disallow old properties
         if (isset($json->{$oldKeyProp})) {
             throw new Exception("'{$oldKeyProp}' property is now '" . $newKeyProp . "'", Z_ERROR_INVALID_INPUT);
         } else {
             if (isset($json->{$oldVersionProp}) && $oldVersionProp != $newVersionProp) {
                 throw new Exception("'{$oldVersionProp}' property is now '" . $newVersionProp . "'", Z_ERROR_INVALID_INPUT);
             }
         }
     } else {
         $keyProp = $oldKeyProp;
         $versionProp = $oldVersionProp;
         // Disallow new properties
         if (isset($json->{$newKeyProp})) {
             throw new Exception("Invalid property '{$newKeyProp}'", Z_ERROR_INVALID_INPUT);
         } else {
             if (isset($json->{$newVersionProp}) && $oldVersionProp != $newVersionProp) {
                 throw new Exception("Invalid property '{$newVersionProp}'", Z_ERROR_INVALID_INPUT);
             }
         }
     }
     if (isset($json->{$versionProp})) {
         if ($requestParams['v'] < 2) {
             throw new Exception("Invalid property '{$versionProp}'", Z_ERROR_INVALID_INPUT);
         }
         if (!is_numeric($json->{$versionProp})) {
             throw new Exception("'{$versionProp}' must be an integer", Z_ERROR_INVALID_INPUT);
         }
         if (!isset($json->{$keyProp}) && $objectType != 'setting' && !$object->key) {
             throw new Exception("'{$versionProp}' is valid only if {$objectType} key is provided", Z_ERROR_INVALID_INPUT);
         }
         $originalVersion = Zotero_Libraries::getOriginalVersion($object->libraryID);
         $updatedVersion = Zotero_Libraries::getUpdatedVersion($object->libraryID);
         // Make sure the object hasn't been modified since the specified version
         if ($object->version > $json->{$versionProp}) {
             // Unless it was modified in this request
             if ($updatedVersion != $originalVersion && $object->version == $updatedVersion) {
                 return;
             }
             throw new HTTPException(ucwords($objectType) . " has been modified since specified version " . "(expected {$json->{$versionProp}}, found {$object->version})", 412);
         } else {
             if ($json->{$versionProp} > 0 && !$object->version) {
                 throw new HTTPException(ucwords($objectType) . " doesn't exist (expected version {$json->{$versionProp}}; use 0 instead)", 404);
             }
         }
     } else {
         if ($requireVersion == 1 && isset($json->{$keyProp})) {
             if ($objectType == 'setting') {
                 throw new HTTPException("Either If-Unmodified-Since-Version or " . "'{$versionProp}' property must be provided", 428);
             } else {
                 throw new HTTPException("Either If-Unmodified-Since-Version or " . "'{$versionProp}' property must be provided for " . "'{$keyProp}'-based writes", 428);
             }
         } else {
             if ($requireVersion == 2) {
                 throw new HTTPException("Either If-Unmodified-Since-Version or " . "'{$versionProp}' property must be provided for " . "single-{$objectType} writes", 428);
             }
         }
     }
 }
Esempio n. 30
0
 /**
  * Used for integration tests
  *
  * Valid only on testing site
  */
 public function clear()
 {
     if (!$this->permissions->isSuper()) {
         $this->e404();
     }
     if (!Z_ENV_TESTING_SITE) {
         $this->e404();
     }
     $this->allowMethods(array('POST'));
     Zotero_Libraries::clearAllData($this->objectLibraryID);
     $this->e204();
 }