The page you requested could not be found.
diff --git a/htdocs/index.php b/htdocs/index.php deleted file mode 100644 index b17d9908..00000000 --- a/htdocs/index.php +++ /dev/null @@ -1,64 +0,0 @@ -. - - ***** END LICENSE BLOCK ***** -*/ - -$params = require('config/routes.inc.php'); - -if (!$params || !isset($params['controller']) || $params['controller'] == 404) { - header("HTTP/1.0 404 Not Found"); - include('errors/404.php'); - return; -} - -// Parse variables from router -$controllerName = ucwords($params['controller']); -$action = !empty($params['action']) ? $params['action'] : lcfirst($controllerName); -$directory = !empty($params['directory']) ? $params['directory'] . '/' : ""; -$extra = !empty($params['extra']) ? $params['extra'] : array(); - -// Attempt to load controller -$controllerFile = Z_ENV_CONTROLLER_PATH . $directory . $controllerName . 'Controller.php'; -Z_Core::debug($_SERVER['REQUEST_METHOD'] . " to " . Z_ENV_SELF); -Z_Core::debug("Controller is $controllerFile"); - -if (file_exists($controllerFile)) { - require('mvc/Controller.inc.php'); - require($controllerFile); - $controllerClass = $controllerName . 'Controller'; - $controller = new $controllerClass($controllerName, $action, $params); - $controller->init($extra); - - if (method_exists($controllerClass, $action)) { - call_user_func(array($controller, $action)); - Z_Core::exitClean(); - } - else { - throw new Exception("Action '$action' not found in $controllerFile"); - } -} - -// If controller not found, load error document -header("HTTP/1.0 404 Not Found"); -include('errors/404.php'); diff --git a/htdocs/license.html b/htdocs/license.html deleted file mode 100644 index aeebb6d2..00000000 --- a/htdocs/license.html +++ /dev/null @@ -1,14 +0,0 @@ -Zotero Data Server
-Copyright © 2010 Center for History and New Media
-George Mason University, Fairfax, Virginia, USA
-http://zotero.org
The Center for History and New Media distributes the Zotero Data Server source code -under the GNU Affero General Public License, version 3 (AGPLv3).
- -The Zotero name is a registered trademark of George Mason University. -See http://zotero.org/trademark for more information.
- -Third-party copyright in this distribution is noted where applicable.
- -All rights not expressly granted are reserved.
diff --git a/htdocs/license.txt b/htdocs/license.txt deleted file mode 100644 index 491c0562..00000000 --- a/htdocs/license.txt +++ /dev/null @@ -1,14 +0,0 @@ -Zotero Data Server -Copyright © 2010 Center for History and New Media -George Mason University, Fairfax, Virginia, USA -http://zotero.org - -The Center for History and New Media distributes the Zotero Data Server source code -under the GNU Affero General Public License, version 3 (AGPLv3). - -The Zotero name is a registered trademark of George Mason University. -See http://zotero.org/trademark for more information. - -Third-party copyright in this distribution is noted where applicable. - -All rights not expressly granted are reserved. diff --git a/include/Base64.inc.php b/include/Base64.inc.php deleted file mode 100644 index 8884ec26..00000000 --- a/include/Base64.inc.php +++ /dev/null @@ -1,131 +0,0 @@ - -// -// Ported from http://www.webtoolkit.info/javascript-base64.html -// -class Z_Base64 { - // private property - private static $keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - // public method for encoding - public static function encode($input) { - $output = ""; - $chr1 = $chr2 = $chr3 = $enc1 = $enc2 = $enc3 = $enc4 = ""; - $i = 0; - - $input = self::utf8_encode($input); - - while ($i < mb_strlen($input)) { - $chr1 = Z_Unicode::charCodeAt($input, $i++); - $chr2 = Z_Unicode::charCodeAt($input, $i++); - $chr3 = Z_Unicode::charCodeAt($input, $i++); - - $enc1 = $chr1 >> 2; - $enc2 = (($chr1 & 3) << 4) | ($chr2 >> 4); - $enc3 = (($chr2 & 15) << 2) | ($chr3 >> 6); - $enc4 = $chr3 & 63; - - if (is_nan($chr2)) { - $enc3 = $enc4 = 64; - } else if (is_nan($chr3)) { - $enc4 = 64; - } - - $output = $output . - Z_Unicode::charAt(self::$keyStr, $enc1) . Z_Unicode::charAt(self::$keyStr, $enc2) . - Z_Unicode::charAt(self::$keyStr, $enc3) . Z_Unicode::charAt(self::$keyStr, $enc4); - - } - - return $output; - } - - // public method for decoding - public static function decode($input) { - $output = ""; - $chr1 = $chr2 = $chr3 = $enc1 = $enc2 = $enc3 = $enc4 = ""; - $i = 0; - - $input = preg_replace('/[^A-Za-z0-9\+\/\=]/', "", $input); - - while ($i < mb_strlen($input)) { - $enc1 = strpos(self::$keyStr, $input[$i++]); - $enc2 = strpos(self::$keyStr, $input[$i++]); - $enc3 = strpos(self::$keyStr, $input[$i++]); - $enc4 = strpos(self::$keyStr, $input[$i++]); - - $chr1 = ($enc1 << 2) | ($enc2 >> 4); - $chr2 = (($enc2 & 15) << 4) | ($enc3 >> 2); - $chr3 = (($enc3 & 3) << 6) | $enc4; - - $output = $output . Z_Unicode::unichr($chr1); - - if ($enc3 != 64) { - $output = $output . Z_Unicode::unichr($chr2); - } - if ($enc4 != 64) { - $output = $output . Z_Unicode::unichr($chr3); - } - } - - $output = self::utf8_decode($output); - - return $output; - } - - // private method for UTF-8 encoding - private static function utf8_encode($string) { - $string = preg_replace('/\r\n/', "\n", $string); - $utftext = ""; - - for ($n = 0; $n < strlen($string); $n++) { - - $c = Z_Unicode::charCodeAt($string, $n); - - if ($c < 128) { - $utftext .= Z_Unicode::fromCharCode($c); - } - else if(($c > 127) && ($c < 2048)) { - $utftext .= Z_Unicode::fromCharCode(($c >> 6) | 192); - $utftext .= Z_Unicode::fromCharCode(($c & 63) | 128); - } - else { - $utftext .= Z_Unicode::fromCharCode(($c >> 12) | 224); - $utftext .= Z_Unicode::fromCharCode((($c >> 6) & 63) | 128); - $utftext .= Z_Unicode::fromCharCode(($c & 63) | 128); - } - - } - - return $utftext; - } - - // private method for UTF-8 decoding - private static function utf8_decode($utftext) { - $string = ""; - $i = 0; - $c = $c1 = $c2 = 0; - - while ( $i < mb_strlen($utftext) ) { - - $c = Z_Unicode::charCodeAt($utftext, $i); - - if ($c < 128) { - $string .= Z_Unicode::fromCharCode($c); - $i++; - } - else if(($c > 191) && ($c < 224)) { - $c2 = Z_Unicode::charCodeAt($utftext, $i+1); - $string .= Z_Unicode::fromCharCode((($c & 31) << 6) | ($c2 & 63)); - $i += 2; - } - else { - $c2 = Z_Unicode::charCodeAt($utftext, $i+1); - $c3 = Z_Unicode::charCodeAt($utftext, $i+2); - $string .= Z_Unicode::fromCharCode((($c & 15) << 12) | (($c2 & 63) << 6) | ($c3 & 63)); - $i += 3; - } - } - - return $string; - } -} diff --git a/include/Core.inc.php b/include/Core.inc.php deleted file mode 100644 index b0104f11..00000000 --- a/include/Core.inc.php +++ /dev/null @@ -1,121 +0,0 @@ - -/* - ***** BEGIN LICENSE BLOCK ***** - - This file is part of the Zotero Data Server. - - Copyright © 2010 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see
- $tr->td->pre = $note;
- }
- }
- }
-
- if ($this->isAttachment()) {
- Zotero_Atom::addHTMLRow(
- $html,
- "linkMode",
- "Link Mode",
- // TODO: Stop returning number
- Zotero_Attachments::linkModeNameToNumber($this->attachmentLinkMode)
- );
- Zotero_Atom::addHTMLRow($html, "mimeType", "MIME Type", $this->attachmentMIMEType);
- Zotero_Atom::addHTMLRow($html, "charset", "Character Set", $this->attachmentCharset);
-
- // TODO: get from a constant
- /*if ($this->attachmentLinkMode != 3) {
- $doc->addField('path', $this->attachmentPath);
- }*/
- }
-
- if ($this->getDeleted()) {
- Zotero_Atom::addHTMLRow($html, "deleted", "Deleted", "Yes");
- }
-
- if (!$requestParams['publications'] && $this->getPublications() ) {
- Zotero_Atom::addHTMLRow($html, "publications", "In My Publications", "Yes");
- }
-
- if ($asSimpleXML) {
- return $html;
- }
-
- return str_replace('', '', $html->asXML());
- }
-
-
- /**
- * Get some uncached properties used by JSON and Atom
- */
- public function getUncachedResponseProps($requestParams, Zotero_Permissions $permissions) {
- $parent = $this->getSource();
- $isRegularItem = !$parent && $this->isRegularItem();
- $downloadDetails = false;
- if ($requestParams['publications'] || $permissions->canAccess($this->libraryID, 'files')) {
- $downloadDetails = Zotero_Storage::getDownloadDetails($this);
- // Link to publications download URL in My Publications
- if ($downloadDetails && $requestParams['publications']) {
- $downloadDetails['url'] = str_replace("/items/", "/publications/items/", $downloadDetails['url']);
- }
- }
- if ($isRegularItem) {
- if ($requestParams['publications']) {
- $numChildren = $this->numPublicationsChildren();
- }
- else if ($permissions->canAccess($this->libraryID, 'notes')) {
- $numChildren = $this->numChildren();
- }
- else {
- $numChildren = $this->numAttachments();
- }
- }
- else {
- $numChildren = false;
- }
-
- return [
- "downloadDetails" => $downloadDetails,
- "numChildren" => $numChildren
- ];
- }
-
-
- 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();
- $isPublications = $requestParams['publications'];
-
- $props = $this->getUncachedResponseProps($requestParams, $permissions);
- $downloadDetails = $props['downloadDetails'];
- $numChildren = $props['numChildren'];
-
- $libraryType = Zotero_Libraries::getType($this->libraryID);
-
- // Any query parameters that have an effect on an individual item's response JSON
- // need to be added here
- $allowedParams = [
- 'include',
- 'style',
- 'css',
- 'linkwrap',
- 'publications'
- ];
- $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) {
- $cached['meta']->numChildren = $numChildren;
- }
-
- 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)
- ];
-
- $url = Zotero_API::getItemURI($this);
- if ($isPublications) {
- $url = str_replace("/items/", "/publications/items/", $url);
- }
- $json['links'] = [
- 'self' => [
- 'href' => $url,
- 'type' => 'application/json'
- ],
- 'alternate' => [
- 'href' => Zotero_URI::getItemURI($this, true),
- 'type' => 'text/html'
- ]
- ];
-
- if ($parent) {
- $parentItem = Zotero_Items::get($this->libraryID, $parent);
- $url = Zotero_API::getItemURI($parentItem);
- if ($isPublications) {
- $url = str_replace("/items/", "/publications/items/", $url);
- }
- $json['links']['up'] = [
- 'href' => $url,
- '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) {
- try {
- $json['meta']->createdByUser = Zotero_Users::toJSON($createdByUserID);
- }
- // If user no longer exists, this will fail
- catch (Exception $e) {
- if (Zotero_Users::exists($createdByUserID)) {
- throw $e;
- }
- }
- }
-
- if ($lastModifiedByUserID && $lastModifiedByUserID != $createdByUserID) {
- try {
- $json['meta']->lastModifiedByUser = Zotero_Users::toJSON($lastModifiedByUserID);
- }
- // If user no longer exists, this will fail
- catch (Exception $e) {
- if (Zotero_Users::exists($lastModifiedByUserID)) {
- throw $e;
- }
- }
- }
- }
-
- 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($requestParams));
- }
- 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)) {
- $exportParams = $requestParams;
- $exportParams['format'] = $type;
- $export = Zotero_Translate::doExport([$this], $exportParams);
- $json[$type] = $export['body'];
- unset($export);
- }
- }
-
- // TEMP
- if ($cached) {
- $cachedStr = Zotero_Utilities::formatJSON($cached);
- $uncachedStr = Zotero_Utilities::formatJSON($json);
- if ($cachedStr != $uncachedStr) {
- error_log("Cached JSON item entry does not match");
- error_log(" Cached: " . $cachedStr);
- error_log("Uncached: " . $uncachedStr);
-
- //Z_Core::$MC->set($cacheKey, $uncached, 3600); // 1 hour for now
- }
- }
- else {
- /*Z_Core::$MC->set($cacheKey, $json, 10);
- StatsD::timing("api.items.itemToResponseJSON.uncached", (microtime(true) - $t) * 1000);
- StatsD::increment("memcached.items.itemToResponseJSON.miss");*/
- }
-
- return $json;
- }
-
-
- public function toJSON($asArray=false, $requestParams=array(), $includeEmpty=false, $unformattedFields=false) {
- $isPublications = !empty($requestParams['publications']);
-
- if ($this->_id || $this->_key) {
- if ($this->_version) {
- // TODO: Check memcache and return if present
- }
-
- if (!$this->loaded['primaryData']) {
- $this->loadPrimaryData();
- }
- if (!$this->loaded['itemData']) {
- $this->loadItemData();
- }
- }
-
- if (!isset($requestParams['v'])) {
- $requestParams['v'] = 3;
- }
-
- $regularItem = $this->isRegularItem();
-
- $arr = array();
- if ($requestParams['v'] >= 2) {
- if ($requestParams['v'] >= 3) {
- $arr['key'] = $this->key;
- $arr['version'] = $this->version;
- }
- else {
- $arr['itemKey'] = $this->key;
- $arr['itemVersion'] = $this->version;
- }
-
- $key = $this->getSourceKey();
- if ($key) {
- $arr['parentItem'] = $key;
- }
- }
- $arr['itemType'] = Zotero_ItemTypes::getName($this->itemTypeID);
-
- if ($this->isAttachment()) {
- $arr['linkMode'] = $this->attachmentLinkMode;
- }
-
- // For regular items, show title and creators first
- if ($regularItem) {
- // Get 'title' or the equivalent base-mapped field
- $titleFieldID = Zotero_ItemFields::getFieldIDFromTypeAndBase($this->itemTypeID, 'title');
- $titleFieldName = Zotero_ItemFields::getName($titleFieldID);
- if ($includeEmpty || $this->itemData[$titleFieldID] !== false) {
- $arr[$titleFieldName] = $this->itemData[$titleFieldID] !== false ? $this->itemData[$titleFieldID] : "";
- }
-
- // Creators
- $arr['creators'] = array();
- $creators = $this->getCreators();
- foreach ($creators as $creator) {
- $c = array();
- $c['creatorType'] = Zotero_CreatorTypes::getName($creator['creatorTypeID']);
-
- // Single-field mode
- if ($creator['ref']->fieldMode == 1) {
- $c['name'] = $creator['ref']->lastName;
- }
- // Two-field mode
- else {
- $c['firstName'] = $creator['ref']->firstName;
- $c['lastName'] = $creator['ref']->lastName;
- }
- $arr['creators'][] = $c;
- }
- if (!$arr['creators'] && !$includeEmpty) {
- unset($arr['creators']);
- }
- }
- else {
- $titleFieldID = false;
- }
-
- // Item metadata
- $fields = array_keys($this->itemData);
- foreach ($fields as $field) {
- if ($field == $titleFieldID) {
- continue;
- }
-
- if ($unformattedFields) {
- $value = $this->itemData[$field];
- }
- else {
- $value = $this->getField($field);
- }
-
- if (!$includeEmpty && ($value === false || $value === "")) {
- continue;
- }
-
- $fieldName = Zotero_ItemFields::getName($field);
- // TEMP
- if ($fieldName == 'versionNumber') {
- if ($requestParams['v'] < 3) {
- $fieldName = 'version';
- }
- }
- else if ($fieldName == 'accessDate') {
- if ($requestParams['v'] >= 3 && $value !== false && $value !== "") {
- $value = Zotero_Date::sqlToISO8601($value);
- }
- }
- $arr[$fieldName] = ($value !== false && $value !== "") ? $value : "";
- }
-
- // Embedded note for notes and attachments
- if (!$regularItem) {
- // Use sanitized version
- $arr['note'] = $this->getNote(true);
- }
-
- if ($this->isAttachment()) {
- $arr['linkMode'] = $this->attachmentLinkMode;
-
- $val = $this->attachmentMIMEType;
- if ($includeEmpty || ($val !== false && $val !== "")) {
- $arr['contentType'] = $val;
- }
-
- $val = $this->attachmentCharset;
- if ($includeEmpty || $val) {
- if ($val) {
- // TODO: Move to CharacterSets::getName() after classic sync removal
- $val = Zotero_CharacterSets::toCanonical($val);
- }
- $arr['charset'] = $val;
- }
-
- if ($this->isImportedAttachment()) {
- $arr['filename'] = $this->attachmentFilename;
-
- $val = $this->attachmentStorageHash;
- if ($includeEmpty || $val) {
- $arr['md5'] = $val;
- }
-
- $val = $this->attachmentStorageModTime;
- if ($includeEmpty || $val) {
- $arr['mtime'] = $val;
- }
- }
- else if ($arr['linkMode'] == 'linked_file') {
- $val = $this->attachmentPath;
- if ($includeEmpty || $val) {
- $arr['path'] = Zotero_Attachments::decodeRelativeDescriptorString($val);
- }
- }
- }
-
- // Non-field properties, which don't get shown for publications endpoints
- if (!$isPublications) {
- if ($this->getDeleted()) {
- $arr['deleted'] = 1;
- }
-
- if ($this->getPublications()) {
- $arr['inPublications'] = true;
- }
-
- // Tags
- $arr['tags'] = array();
- $tags = $this->getTags();
- if ($tags) {
- foreach ($tags as $tag) {
- // Skip empty tags that are still in the database
- if (!trim($tag->name)) {
- continue;
- }
- $t = array(
- 'tag' => $tag->name
- );
- if ($tag->type != 0) {
- $t['type'] = $tag->type;
- }
- $arr['tags'][] = $t;
- }
- }
-
- if ($requestParams['v'] >= 2) {
- if ($this->isTopLevelItem()) {
- $collections = $this->getCollections(true);
- $arr['collections'] = $collections;
- }
-
- $arr['relations'] = $this->getRelations();
- }
-
- if ($requestParams['v'] >= 3) {
- $arr['dateAdded'] = Zotero_Date::sqlToISO8601($this->dateAdded);
- $arr['dateModified'] = Zotero_Date::sqlToISO8601($this->dateModified);
- }
- }
-
- if ($asArray) {
- return $arr;
- }
-
- // Before v3, additional characters were escaped in the JSON, for unclear reasons
- $escapeAll = $requestParams['v'] <= 2;
-
- return Zotero_Utilities::formatJSON($arr, $escapeAll);
- }
-
-
- public function toCSLItem() {
- return Zotero_Cite::retrieveItem($this);
- }
-
-
- //
- //
- // Private methods
- //
- //
- protected function loadItemData($reload = false) {
- if ($this->loaded['itemData'] && !$reload) return;
-
- Z_Core::debug("Loading item data for item $this->id");
-
- // TODO: remove?
- if (!$this->id) {
- trigger_error('Item ID not set before attempting to load data', E_USER_ERROR);
- }
-
- if (!is_numeric($this->id)) {
- trigger_error("Invalid itemID '$this->id'", E_USER_ERROR);
- }
-
- if ($this->cacheEnabled) {
- $cacheVersion = 1;
- $cacheKey = $this->getCacheKey("itemData",
- $cacheVersion
- . isset(Z_CONFIG::$CACHE_VERSION_ITEM_DATA)
- ? "_" . Z_CONFIG::$CACHE_VERSION_ITEM_DATA
- : ""
- );
- $fields = Z_Core::$MC->get($cacheKey);
- }
- else {
- $fields = false;
- }
- if ($fields === false) {
- $sql = "SELECT fieldID, value FROM itemData WHERE itemID=?";
- $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
- $fields = Zotero_DB::queryFromStatement($stmt, $this->id);
-
- if ($this->cacheEnabled) {
- Z_Core::$MC->set($cacheKey, $fields ? $fields : array());
- }
- }
-
- $itemTypeFields = Zotero_ItemFields::getItemTypeFields($this->itemTypeID);
-
- if ($fields) {
- foreach ($fields as $field) {
- $this->setField($field['fieldID'], $field['value'], true, true);
- }
- }
-
- // Mark nonexistent fields as loaded
- if ($itemTypeFields) {
- foreach($itemTypeFields as $fieldID) {
- if (is_null($this->itemData[$fieldID])) {
- $this->itemData[$fieldID] = false;
- }
- }
- }
-
- $this->loaded['itemData'] = true;
- }
-
-
- protected function loadNote($reload = false) {
- if ($this->loaded['note'] && !$reload) return;
-
- $this->noteTitle = null;
- $this->noteText = null;
-
- // Loaded in getNote()
- }
-
-
- private function getNoteHash() {
- if (!$this->isNote() && !$this->isAttachment()) {
- trigger_error("getNoteHash() can only be called on notes and attachments", E_USER_ERROR);
- }
-
- if (!$this->id) {
- return '';
- }
-
- // Store access time for later garbage collection
- //$this->noteAccessTime = new Date();
-
- return Zotero_Notes::getHash($this->libraryID, $this->id);
- }
-
-
- protected function loadCreators($reload = false) {
- if ($this->loaded['creators'] && !$reload) return;
-
- if (!$this->id) {
- trigger_error('Item ID not set for item before attempting to load creators', E_USER_ERROR);
- }
-
- if (!is_numeric($this->id)) {
- trigger_error("Invalid itemID '$this->id'", E_USER_ERROR);
- }
-
- if ($this->cacheEnabled) {
- $cacheVersion = 1;
- $cacheKey = $this->getCacheKey("itemCreators",
- $cacheVersion
- . isset(Z_CONFIG::$CACHE_VERSION_ITEM_DATA)
- ? "_" . Z_CONFIG::$CACHE_VERSION_ITEM_DATA
- : ""
- );
- $creators = Z_Core::$MC->get($cacheKey);
- }
- else {
- $creators = false;
- }
- if ($creators === false) {
- $sql = "SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators
- WHERE itemID=? ORDER BY orderIndex";
- $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
- $creators = Zotero_DB::queryFromStatement($stmt, $this->id);
-
- if ($this->cacheEnabled) {
- Z_Core::$MC->set($cacheKey, $creators ? $creators : array());
- }
- }
-
- $this->creators = [];
- $this->loaded['creators'] = true;
- $this->clearChanged('creators');
-
- if (!$creators) {
- return;
- }
-
- foreach ($creators as $creator) {
- $creatorObj = Zotero_Creators::get($this->libraryID, $creator['creatorID'], true);
- if (!$creatorObj) {
- Z_Core::$MC->delete($cacheKey);
- throw new Exception("Creator {$creator['creatorID']} not found");
- }
- $this->creators[$creator['orderIndex']] = array(
- 'creatorTypeID' => $creator['creatorTypeID'],
- 'ref' => $creatorObj
- );
- }
- }
-
-
- protected function loadCollections($reload = false) {
- if ($this->loaded['collections'] && !$reload) return;
-
- if (!$this->id) {
- return;
- }
-
- Z_Core::debug("Loading collections for item $this->id");
-
- $sql = "SELECT C.key FROM collectionItems "
- . "JOIN collections C USING (collectionID) "
- . "WHERE itemID=?";
- $this->collections = Zotero_DB::columnQuery(
- $sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID)
- );
- if (!$this->collections) {
- $this->collections = [];
- }
- $this->loaded['collections'] = true;
- $this->clearChanged('collections');
- }
-
-
- protected function loadTags($reload = false) {
- if ($this->loaded['tags'] && !$reload) return;
-
- if (!$this->id) {
- return;
- }
-
- Z_Core::debug("Loading tags for item $this->id");
-
- $sql = "SELECT tagID FROM itemTags JOIN tags USING (tagID) WHERE itemID=?";
- $tagIDs = Zotero_DB::columnQuery(
- $sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID)
- );
- $this->tags = [];
- if ($tagIDs) {
- foreach ($tagIDs as $tagID) {
- $this->tags[] = Zotero_Tags::get($this->libraryID, $tagID, true);
- }
- }
- $this->loaded['tags'] = true;
- $this->clearChanged('tags');
- }
-
-
- /**
- * @return {array} An array of related item keys
- */
- private function getRelatedItems() {
- $predicate = Zotero_Relations::$relatedItemPredicate;
-
- $relations = $this->getRelations();
- if (empty($relations->$predicate)) {
- return [];
- }
-
- $relatedItemURIs = is_string($relations->$predicate)
- ? [$relations->$predicate]
- : $relations->$predicate;
-
- // Pull out object values from related-item relations, turn into items, and pull out keys
- $keys = [];
- foreach ($relatedItemURIs as $relatedItemURI) {
- $item = Zotero_URI::getURIItem($relatedItemURI);
- if ($item) {
- $keys[] = $item->key;
- }
- }
- return array_unique($keys);
- }
-
-
- /**
- * @param {array} $itemKeys
- * @return {Boolean} TRUE if related items were changed, FALSE if not
- */
- private function setRelatedItems($itemKeys) {
- if (!is_array($itemKeys)) {
- throw new Exception('$itemKeys must be an array');
- }
-
- $predicate = Zotero_Relations::$relatedItemPredicate;
-
- $relations = $this->getRelations();
- if (!isset($relations->$predicate)) {
- $relations->$predicate = [];
- }
- else if (is_string($relations->$predicate)) {
- $relations->$predicate = [$relations->$predicate];
- }
-
- $currentKeys = array_map(function ($objectURI) {
- $key = substr($objectURI, -8);
- return Zotero_ID::isValidKey($key) ? $key : false;
- }, $relations->$predicate);
- $currentKeys = array_filter($currentKeys);
-
- $oldKeys = []; // items being kept
- $newKeys = []; // new items
-
- if (!$itemKeys) {
- if (!$currentKeys) {
- Z_Core::debug("No related items added", 4);
- return false;
- }
- }
- else {
- foreach ($itemKeys as $itemKey) {
- if ($itemKey == $this->key) {
- Z_Core::debug("Can't relate item to itself in Zotero.Item.setRelatedItems()", 2);
- continue;
- }
-
- if (in_array($itemKey, $currentKeys)) {
- Z_Core::debug("Item {$this->key} is already related to item $itemKey");
- $oldKeys[] = $itemKey;
- continue;
- }
-
- // TODO: check if related on other side (like client)?
-
- $newKeys[] = $itemKey;
- }
- }
-
- // If new or changed keys, update relations with new related items
- if ($newKeys || sizeOf($oldKeys) != sizeOf($currentKeys)) {
- $prefix = Zotero_URI::getLibraryURI($this->libraryID) . "/items/";
- $relations->$predicate = array_map(function ($key) use ($prefix) {
- return $prefix . $key;
- }, array_merge($oldKeys, $newKeys));
- $this->setRelations($relations);
- return true;
- }
- else {
- Z_Core::debug('Related items not changed', 4);
- return false;
- }
- }
-
-
- protected function loadRelations($reload = false) {
- if ($this->loaded['relations'] && !$reload) return;
-
- if (!$this->id) {
- return;
- }
-
- Z_Core::debug("Loading relations for item $this->id");
-
- $this->loadPrimaryData(false, true);
-
- $itemURI = Zotero_URI::getItemURI($this);
-
- $relations = Zotero_Relations::getByURIs($this->libraryID, $itemURI);
- $relations = array_map(function ($rel) {
- return [$rel->predicate, $rel->object];
- }, $relations);
-
- // Related items are bidirectional, so include any with this item as the object
- $reverseRelations = Zotero_Relations::getByURIs(
- $this->libraryID, false, Zotero_Relations::$relatedItemPredicate, $itemURI
- );
- foreach ($reverseRelations as $rel) {
- $r = [$rel->predicate, $rel->subject];
- // Only add if not already added in other direction
- if (!in_array($r, $relations)) {
- $relations[] = $r;
- }
- }
-
- // Also include any owl:sameAs relations with this item as the object
- // (as sent by client via classic sync)
- $reverseRelations = Zotero_Relations::getByURIs(
- $this->libraryID, false, Zotero_Relations::$linkedObjectPredicate, $itemURI
- );
- foreach ($reverseRelations as $rel) {
- $relations[] = [$rel->predicate, $rel->subject];
- }
-
- // TEMP: Get old-style related items
- //
- // Add related items
- $sql = "SELECT `key` FROM itemRelated IR "
- . "JOIN items I ON (IR.linkedItemID=I.itemID) "
- . "WHERE IR.itemID=?";
- $relatedItemKeys = Zotero_DB::columnQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
- if ($relatedItemKeys) {
- $prefix = Zotero_URI::getLibraryURI($this->libraryID) . "/items/";
- $predicate = Zotero_Relations::$relatedItemPredicate;
- foreach ($relatedItemKeys as $key) {
- $relations[] = [$predicate, $prefix . $key];
- }
- }
- // Reverse as well
- $sql = "SELECT `key` FROM itemRelated IR JOIN items I USING (itemID) WHERE IR.linkedItemID=?";
- $reverseRelatedItemKeys = Zotero_DB::columnQuery(
- $sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID)
- );
- if ($reverseRelatedItemKeys) {
- $prefix = Zotero_URI::getLibraryURI($this->libraryID) . "/items/";
- $predicate = Zotero_Relations::$relatedItemPredicate;
- foreach ($reverseRelatedItemKeys as $key) {
- $relations[] = [$predicate, $prefix . $key];
- }
- }
-
- $this->relations = $relations;
- $this->loaded['relations'] = true;
- $this->clearChanged('relations');
- }
-
-
- private function getETag() {
- if (!$this->loaded['primaryData']) {
- $this->loadPrimaryData();
- }
- return md5($this->serverDateModified . $this->version);
- }
-
-
- private function getCacheKey($mode, $cacheVersion=false) {
- if (!$this->loaded['primaryData']) {
- $this->loadPrimaryData();
- }
-
- if (!$this->id) {
- return false;
- }
- if (!$mode) {
- throw new Exception('$mode not provided');
- }
- return $mode
- . "_". $this->id
- . "_" . $this->version
- . ($cacheVersion ? "_" . $cacheVersion : "");
- }
-
-
- /**
- * Throw if item is a top-level attachment and isn't either a file attachment (imported or linked)
- * or an imported web PDF
- *
- * NOTE: This is currently unused, because 1) these items still exist in people's databases from
- * early Zotero versions (and could be modified and uploaded at any time) and 2) it's apparently
- * still possible to create them on Linux/Windows by dragging child items out, which is a bug.
- * In any case, if this were to be enforced, the client would need to properly prevent that on all
- * platforms, convert those items in a schema update step by adding parent items (which would
- * probably make people unhappy (though so would things breaking because we forgot they existed in
- * old databases)), and old clients would need to be cut off from syncing.
- */
- private function checkTopLevelAttachment() {
- if (!$this->isAttachment()) {
- return;
- }
- if ($this->getSourceKey()) {
- return;
- }
- $linkMode = $this->attachmentLinkMode;
- if ($linkMode == 'linked_url'
- || ($linkMode == 'imported_url' && $this->attachmentContentType != 'application/pdf')) {
- throw new Exception("Only file attachments and PDFs can be top-level items", Z_ERROR_INVALID_INPUT);
- }
- }
-}
-?>
diff --git a/model/ItemFields.inc.php b/model/ItemFields.inc.php
deleted file mode 100644
index bf3bd16d..00000000
--- a/model/ItemFields.inc.php
+++ /dev/null
@@ -1,619 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_ItemFields {
- private static $customFieldCheck = array();
-
- // Caches
- private static $fieldIDCache = array();
- private static $fieldNameCache = array();
- private static $itemTypeFieldsCache = array();
- private static $itemTypeBaseFieldIDCache = array();
- private static $isValidForTypeCache = array();
- private static $isBaseFieldCache = array();
- private static $typeFieldIDsByBaseCache = array();
- private static $typeFieldNamesByBaseCache = array();
-
- private static $localizedFields = array(
- "itemType" => "Type",
- "title" => "Title",
- "dateAdded" => "Date Added",
- "dateModified" => "Modified",
- "source" => "Source",
- "notes" => "Notes",
- "tags" => "Tags",
- "attachments" => "Attachments",
- "related" => "Related",
- "url" => "URL",
- "rights" => "Rights",
- "series" => "Series",
- "volume" => "Volume",
- "issue" => "Issue",
- "edition" => "Edition",
- "place" => "Place",
- "publisher" => "Publisher",
- "pages" => "Pages",
- "ISBN" => "ISBN",
- "publicationTitle" => "Publication",
- "ISSN" => "ISSN",
- "date" => "Date",
- "section" => "Section",
- "callNumber" => "Call Number",
- "archiveLocation" => "Loc. in Archive",
- "distributor" => "Distributor",
- "extra" => "Extra",
- "journalAbbreviation" => "Journal Abbr",
- "DOI" => "DOI",
- "accessDate" => "Accessed",
- "seriesTitle" => "Series Title",
- "seriesText" => "Series Text",
- "seriesNumber" => "Series Number",
- "institution" => "Institution",
- "reportType" => "Report Type",
- "code" => "Code",
- "session" => "Session",
- "legislativeBody" => "Legislative Body",
- "history" => "History",
- "reporter" => "Reporter",
- "court" => "Court",
- "numberOfVolumes" => "# of Volumes",
- "committee" => "Committee",
- "assignee" => "Assignee",
- "patentNumber" => "Patent Number",
- "priorityNumbers" => "Priority Numbers",
- "issueDate" => "Issue Date",
- "references" => "References",
- "legalStatus" => "Legal Status",
- "codeNumber" => "Code Number",
- "artworkMedium" => "Medium",
- "number" => "Number",
- "artworkSize" => "Artwork Size",
- "libraryCatalog" => "Library Catalog",
- "repository" => "Repository",
- "videoRecordingFormat" => "Format",
- "interviewMedium" => "Medium",
- "letterType" => "Type",
- "manuscriptType" => "Type",
- "mapType" => "Type",
- "scale" => "Scale",
- "thesisType" => "Type",
- "websiteType" => "Website Type",
- "audioRecordingFormat" => "Format",
- "label" => "Label",
- "presentationType" => "Type",
- "meetingName" => "Meeting Name",
- "studio" => "Studio",
- "runningTime" => "Running Time",
- "network" => "Network",
- "postType" => "Post Type",
- "audioFileType" => "File Type",
- "versionNumber" => "Version",
- "system" => "System",
- "company" => "Company",
- "conferenceName" => "Conference Name",
- "encyclopediaTitle" => "Encyclopedia Title",
- "dictionaryTitle" => "Dictionary Title",
- "language" => "Language",
- "programmingLanguage" => "Language",
- "university" => "University",
- "abstractNote" => "Abstract",
- "websiteTitle" => "Website Title",
- "reportNumber" => "Report Number",
- "billNumber" => "Bill Number",
- "codeVolume" => "Code Volume",
- "codePages" => "Code Pages",
- "dateDecided" => "Date Decided",
- "reporterVolume" => "Reporter Volume",
- "firstPage" => "First Page",
- "documentNumber" => "Document Number",
- "dateEnacted" => "Date Enacted",
- "publicLawNumber" => "Public Law Number",
- "country" => "Country",
- "applicationNumber" => "Application Number",
- "forumTitle" => "Forum/Listserv Title",
- "episodeNumber" => "Episode Number",
- "blogTitle" => "Blog Title",
- "medium" => "Medium",
- "caseName" => "Case Name",
- "nameOfAct" => "Name of Act",
- "subject" => "Subject",
- "proceedingsTitle" => "Proceedings Title",
- "bookTitle" => "Book Title",
- "shortTitle" => "Short Title",
- "docketNumber" => "Docket Number",
- "numPages" => "# of Pages",
- "programTitle" => "Program Title",
- "issuingAuthority" => "Issuing Authority",
- "filingDate" => "Filing Date",
- "genre" => "Genre",
- "archive" => "Archive"
- );
-
-
- public static function getID($fieldOrFieldID) {
- // TODO: batch load
-
- if (isset(self::$fieldIDCache[$fieldOrFieldID])) {
- return self::$fieldIDCache[$fieldOrFieldID];
- }
-
- $cacheKey = "itemFieldID_" . $fieldOrFieldID;
- $fieldID = Z_Core::$MC->get($cacheKey);
- if ($fieldID) {
- // casts are temporary until memcached reload
- self::$fieldIDCache[$fieldOrFieldID] = (int) $fieldID;
- return (int) $fieldID;
- }
-
- $sql = "(SELECT fieldID FROM fields WHERE fieldID=?) UNION
- (SELECT fieldID FROM fields WHERE fieldName=?) LIMIT 1";
- $fieldID = Zotero_DB::valueQuery($sql, array($fieldOrFieldID, $fieldOrFieldID));
-
- self::$fieldIDCache[$fieldOrFieldID] = $fieldID ? (int) $fieldID : false;
- Z_Core::$MC->set($cacheKey, (int) $fieldID);
-
- return $fieldID ? (int) $fieldID : false;
- }
-
-
- public static function getName($fieldOrFieldID) {
- if (isset(self::$fieldNameCache[$fieldOrFieldID])) {
- return self::$fieldNameCache[$fieldOrFieldID];
- }
-
- $cacheVersion = 1;
-
- $cacheKey = "itemFieldName_" . $fieldOrFieldID . "_$cacheVersion";
- $fieldName = Z_Core::$MC->get($cacheKey);
- if ($fieldName) {
- self::$fieldNameCache[$fieldOrFieldID] = $fieldName;
- return $fieldName;
- }
-
- $sql = "(SELECT fieldName FROM fields WHERE fieldID=?) UNION
- (SELECT fieldName FROM fields WHERE fieldName=?) LIMIT 1";
- $fieldName = Zotero_DB::valueQuery($sql, array($fieldOrFieldID, $fieldOrFieldID));
-
- self::$fieldNameCache[$fieldOrFieldID] = $fieldName;
- Z_Core::$MC->set($cacheKey, $fieldName);
-
- return $fieldName;
- }
-
-
- public static function getLocalizedString($itemType, $field, $locale='en-US') {
- if ($locale != 'en-US') {
- throw new Exception("Locale not yet supported");
- }
-
- // Fields in the items table are special cases
- switch ($field) {
- case 'dateAdded':
- case 'dateModified':
- case 'itemType':
- $fieldName = $field;
- break;
-
- default:
- // unused currently
- //var typeName = Zotero.ItemTypes.getName(itemType);
- $fieldName = self::getName($field);
- }
-
- // TODO: different labels for different item types
-
- return self::$localizedFields[$fieldName];
- }
-
-
- public static function getAll($locale=false) {
- $sql = "SELECT DISTINCT fieldID AS id, fieldName AS name
- FROM fields JOIN itemTypeFields USING (fieldID)";
- // TEMP - skip nsfReviewer fields
- $sql .= " WHERE fieldID < 10000";
- $rows = Zotero_DB::query($sql);
-
- // TODO: cache
-
- if (!$locale) {
- return $rows;
- }
-
- foreach ($rows as &$row) {
- $row['localized'] = self::getLocalizedString(false, $row['id'], $locale);
- }
-
- usort($rows, function ($a, $b) {
- return strcmp($a["localized"], $b["localized"]);
- });
-
- return $rows;
- }
-
-
- /**
- * Validates field content
- *
- * @param string $field Field name
- * @param mixed $value
- * @return bool
- */
- public static function validate($field, $value) {
- if (empty($field)) {
- throw new Exception("Field not provided");
- }
-
- switch ($field) {
- case 'itemTypeID':
- return is_integer($value);
- }
-
- return true;
- }
-
-
- public static function isValidForType($fieldID, $itemTypeID) {
- // Check local cache
- if (isset(self::$isValidForTypeCache[$itemTypeID][$fieldID])) {
- return self::$isValidForTypeCache[$itemTypeID][$fieldID];
- }
-
- // Check memcached
- $cacheKey = "isValidForType_" . $itemTypeID . "_" . $fieldID;
- $valid = Z_Core::$MC->get($cacheKey);
- if ($valid !== false) {
- if (!isset(self::$isValidForTypeCache[$itemTypeID])) {
- self::$isValidForTypeCache[$itemTypeID] = array();
- }
- self::$isValidForTypeCache[$itemTypeID][$fieldID] = !!$valid;
- return !!$valid;
- }
-
- if (!self::getID($fieldID)) {
- throw new Exception("Invalid fieldID '$fieldID'");
- }
-
- if (!Zotero_ItemTypes::getID($itemTypeID)) {
- throw new Exception("Invalid item type id '$itemTypeID'");
- }
-
- $sql = "SELECT COUNT(*) FROM itemTypeFields WHERE itemTypeID=? AND fieldID=?";
- $valid = !!Zotero_DB::valueQuery($sql, array($itemTypeID, $fieldID));
-
- // Store in local cache and memcached
- if (!isset(self::$isValidForTypeCache[$itemTypeID])) {
- self::$isValidForTypeCache[$itemTypeID] = array();
- }
- self::$isValidForTypeCache[$itemTypeID][$fieldID] = $valid;
- Z_Core::$MC->set($cacheKey, $valid ? true : 0);
-
- return $valid;
- }
-
-
- public static function getItemTypeFields($itemTypeID) {
- if (isset(self::$itemTypeFieldsCache[$itemTypeID])) {
- return self::$itemTypeFieldsCache[$itemTypeID];
- }
-
- $cacheKey = "itemTypeFields_" . $itemTypeID;
- $fields = Z_Core::$MC->get($cacheKey);
- if ($fields !== false) {
- self::$itemTypeFieldsCache[$itemTypeID] = $fields;
- return $fields;
- }
-
- if (!Zotero_ItemTypes::getID($itemTypeID)) {
- throw new Exception("Invalid item type id '$itemTypeID'");
- }
-
- $sql = 'SELECT fieldID FROM itemTypeFields WHERE itemTypeID=? ORDER BY orderIndex';
- $fields = Zotero_DB::columnQuery($sql, $itemTypeID);
- if (!$fields) {
- $fields = array();
- }
-
- self::$itemTypeFieldsCache[$itemTypeID] = $fields;
- Z_Core::$MC->set($cacheKey, $fields);
-
- return $fields;
- }
-
-
- public static function isBaseField($field) {
- $fieldID = self::getID($field);
- if (!$fieldID) {
- throw new Exception("Invalid field '$field'");
- }
-
- if (isset(self::$isBaseFieldCache[$fieldID])) {
- return self::$isBaseFieldCache[$fieldID];
- }
-
- $cacheKey = "isBaseField_" . $fieldID;
- $isBase = Z_Core::$MC->get($cacheKey);
- if ($isBase !== false) {
- self::$isBaseFieldCache[$fieldID] = !!$isBase;
- return !!$isBase;
- }
-
- $sql = "SELECT COUNT(*) FROM baseFieldMappings WHERE baseFieldID=?";
- $isBase = !!Zotero_DB::valueQuery($sql, $fieldID);
-
- self::$isBaseFieldCache[$fieldID] = $isBase;
- // Store in memcached (and store FALSE as 0)
- Z_Core::$MC->set($cacheKey, $isBase ? true : 0);
-
- return $isBase;
- }
-
-
- public static function isFieldOfBase($field, $baseField) {
- $fieldID = self::getID($field);
- if (!$fieldID) {
- throw new Exception("Invalid field '$field'");
- }
-
- $baseFieldID = self::getID($baseField);
- if (!$baseFieldID) {
- throw new Exception("Invalid field '$baseField' for base field");
- }
-
- if ($fieldID == $baseFieldID) {
- return true;
- }
-
- $typeFields = self::getTypeFieldsFromBase($baseFieldID);
- return in_array($fieldID, $typeFields);
- }
-
-
- public static function getBaseMappedFields() {
- $sql = "SELECT DISTINCT fieldID FROM baseFieldMappings";
- return Zotero_DB::columnQuery($sql);
- }
-
-
- /*
- * Returns the fieldID of a type-specific field for a given base field
- * or false if none
- *
- * Examples:
- *
- * 'audioRecording' and 'publisher' returns label's fieldID
- * 'book' and 'publisher' returns publisher's fieldID
- * 'audioRecording' and 'number' returns false
- *
- * Accepts names or ids
- */
- public static function getFieldIDFromTypeAndBase($itemType, $baseField) {
- $itemTypeID = Zotero_ItemTypes::getID($itemType);
- $baseFieldID = self::getID($baseField);
-
- // Check local cache
- if (isset(self::$itemTypeBaseFieldIDCache[$itemTypeID][$baseFieldID])) {
- return self::$itemTypeBaseFieldIDCache[$itemTypeID][$baseFieldID];
- }
-
- // Check memcached
- $cacheKey = "itemTypeBaseFieldID_" . $itemTypeID . "_" . $baseFieldID;
- $fieldID = Z_Core::$MC->get($cacheKey);
- if ($fieldID !== false) {
- if (!isset(self::$itemTypeBaseFieldIDCache[$itemTypeID])) {
- self::$itemTypeBaseFieldIDCache[$itemTypeID] = array();
- }
- // FALSE is stored as 0
- if ($fieldID == 0) {
- $fieldID = false;
- }
- self::$itemTypeBaseFieldIDCache[$itemTypeID][$baseFieldID] = $fieldID;
- return $fieldID;
- }
-
- if (!$itemTypeID) {
- throw new Exception("Invalid item type '$itemType'");
- }
-
- if (!$baseFieldID) {
- throw new Exception("Invalid field '$baseField' for base field");
- }
-
- $sql = "SELECT fieldID FROM baseFieldMappings
- WHERE itemTypeID=? AND baseFieldID=?
- UNION
- SELECT baseFieldID FROM baseFieldMappings
- WHERE itemTypeID=? AND baseFieldID=?";
- $fieldID = Zotero_DB::valueQuery(
- $sql,
- array($itemTypeID, $baseFieldID, $itemTypeID, $baseFieldID)
- );
-
- if (!$fieldID) {
- if (self::isBaseField($baseFieldID) && self::isValidForType($baseFieldID, $itemTypeID)) {
- $fieldID = $baseFieldID;
- }
- }
-
- // Store in local cache
- if (!isset(self::$itemTypeBaseFieldIDCache[$itemTypeID])) {
- self::$itemTypeBaseFieldIDCache[$itemTypeID] = array();
- }
- self::$itemTypeBaseFieldIDCache[$itemTypeID][$baseFieldID] = $fieldID;
- // Store in memcached (and store FALSE as 0)
- Z_Core::$MC->set($cacheKey, $fieldID ? $fieldID : 0);
-
- return $fieldID;
- }
-
-
- /*
- * Returns the fieldID of the base field for a given type-specific field
- * or false if none
- *
- * Examples:
- *
- * 'audioRecording' and 'label' returns publisher's fieldID
- * 'book' and 'publisher' returns publisher's fieldID
- * 'audioRecording' and 'runningTime' returns false
- *
- * Accepts names or ids
- */
- public static function getBaseIDFromTypeAndField($itemType, $typeField) {
- $itemTypeID = Zotero_ItemTypes::getID($itemType);
- if (!$itemTypeID) {
- throw new Exception("Invalid item type '$itemType'");
- }
-
- $typeFieldID = self::getID($typeField);
- if (!$typeFieldID) {
- throw new Exception("Invalid field '$typeField'");
- }
-
- if (!self::isValidForType($typeFieldID, $itemTypeID)) {
- throw new Exception("'$typeField' is not a valid field for item type '$itemType'");
- }
-
- // If typeField is already a base field, just return that
- if (self::isBaseField($typeFieldID)) {
- return $typeFieldID;
- }
-
- return Zotero_DB::valueQuery("SELECT baseFieldID FROM baseFieldMappings
- WHERE itemTypeID=? AND fieldID=?", array($itemTypeID, $typeFieldID));
- }
-
-
- /*
- * Returns an array of fieldIDs associated with a given base field
- *
- * e.g. 'publisher' returns fieldIDs for [university, studio, label, network]
- */
- public static function getTypeFieldsFromBase($baseField, $asNames=false) {
- $baseFieldID = self::getID($baseField);
- if (!$baseFieldID) {
- throw new Exception("Invalid base field '$baseField'");
- }
-
- if ($asNames) {
- if (isset(self::$typeFieldNamesByBaseCache[$baseFieldID])) {
- return self::$typeFieldNamesByBaseCache[$baseFieldID];
- }
-
- $cacheKey = "itemTypeFieldNamesByBase_" . $baseFieldID;
- $fieldNames = Z_Core::$MC->get($cacheKey);
- if ($fieldNames) {
- self::$typeFieldNamesByBaseCache[$baseFieldID] = $fieldNames;
- return $fieldNames;
- }
-
- $sql = "SELECT fieldName FROM fields WHERE fieldID IN (
- SELECT fieldID FROM baseFieldMappings
- WHERE baseFieldID=?)";
- $fieldNames = Zotero_DB::columnQuery($sql, $baseFieldID);
- if (!$fieldNames) {
- $fieldNames = array();
- }
-
- self::$typeFieldNamesByBaseCache[$baseFieldID] = $fieldNames;
- Z_Core::$MC->set($cacheKey, $fieldNames);
-
- return $fieldNames;
- }
-
- // TEMP
- if ($baseFieldID==14) {
- return array(96,52,100,10008);
- }
-
- if (isset(self::$typeFieldIDsByBaseCache[$baseFieldID])) {
- return self::$typeFieldIDsByBaseCache[$baseFieldID];
- }
-
- $cacheKey = "itemTypeFieldIDsByBase_" . $baseFieldID;
- $fieldIDs = Z_Core::$MC->get($cacheKey);
- if ($fieldIDs) {
- self::$typeFieldIDsByBaseCache[$baseFieldID] = $fieldIDs;
- return $fieldIDs;
- }
-
- $sql = "SELECT DISTINCT fieldID FROM baseFieldMappings WHERE baseFieldID=?";
- $fieldIDs = Zotero_DB::columnQuery($sql, $baseFieldID);
- if (!$fieldIDs) {
- $fieldIDs = array();
- }
-
- self::$typeFieldIDsByBaseCache[$baseFieldID] = $fieldIDs;
- Z_Core::$MC->set($cacheKey, $fieldIDs);
-
- return $fieldIDs;
- }
-
-
- public static function isCustomField($fieldID) {
- if (isset(self::$customFieldCheck)) {
- return self::$customFieldCheck;
- }
-
- $sql = "SELECT custom FROM fields WHERE fieldID=?";
- $isCustom = Zotero_DB::valueQuery($sql, $fieldID);
- if ($isCustom === false) {
- throw new Exception("Invalid fieldID '$fieldID'");
- }
-
- self::$customFieldCheck[$fieldID] = !!$isCustom;
-
- return !!$isCustom;
- }
-
-
- public static function addCustomField($name) {
- if (self::getID($name)) {
- trigger_error("Field '$name' already exists", E_USER_ERROR);
- }
-
- if (!preg_match('/^[a-z][^\s0-9]+$/', $name)) {
- trigger_error("Invalid field name '$name'", E_USER_ERROR);
- }
-
- // TODO: make sure user hasn't added too many already
-
- trigger_error("Unimplemented", E_USER_ERROR);
- // TODO: add to cache
-
- Zotero_DB::beginTransaction();
-
- $sql = "SELECT NEXT_ID(fieldID) FROM fields";
- $fieldID = Zotero_DB::valueQuery($sql);
-
- $sql = "INSERT INTO fields (?, ?, ?)";
- Zotero_DB::query($sql, array($fieldID, $name, 1));
-
- Zotero_DB::commit();
-
- return $fieldID;
- }
-}
-?>
diff --git a/model/ItemTypes.inc.php b/model/ItemTypes.inc.php
deleted file mode 100644
index a576e7e3..00000000
--- a/model/ItemTypes.inc.php
+++ /dev/null
@@ -1,272 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_ItemTypes {
- private static $typeIDs = array();
- private static $typeNames = array();
- private static $customTypeCheck = array();
-
- private static $localizedStrings = array(
- "note" => "Note",
- "attachment" => "Attachment",
- "book" => "Book",
- "bookSection" => "Book Section",
- "journalArticle" => "Journal Article",
- "magazineArticle" => "Magazine Article",
- "newspaperArticle" => "Newspaper Article",
- "thesis" => "Thesis",
- "letter" => "Letter",
- "manuscript" => "Manuscript",
- "interview" => "Interview",
- "film" => "Film",
- "artwork" => "Artwork",
- "webpage" => "Web Page",
- "report" => "Report",
- "bill" => "Bill",
- "case" => "Case",
- "hearing" => "Hearing",
- "patent" => "Patent",
- "statute" => "Statute",
- "email" => "E-mail",
- "map" => "Map",
- "blogPost" => "Blog Post",
- "instantMessage" => "Instant Message",
- "forumPost" => "Forum Post",
- "audioRecording" => "Audio Recording",
- "presentation" => "Presentation",
- "videoRecording" => "Video Recording",
- "tvBroadcast" => "TV Broadcast",
- "radioBroadcast" => "Radio Broadcast",
- "podcast" => "Podcast",
- "computerProgram" => "Computer Program",
- "conferencePaper" => "Conference Paper",
- "document" => "Document",
- "encyclopediaArticle" => "Encyclopedia Article",
- "dictionaryEntry" => "Dictionary Entry",
- "nsfReviewer" => "NSF Reviewer"
- );
-
- public static function getID($typeOrTypeID) {
- if (!$typeOrTypeID) {
- throw new Exception("An item type id or name must be provided");
- }
-
- if (isset(self::$typeIDs[$typeOrTypeID])) {
- return self::$typeIDs[$typeOrTypeID];
- }
-
- $cacheKey = "itemTypeID_" . $typeOrTypeID;
- $typeID = Z_Core::$MC->get($cacheKey);
- if ($typeID) {
- // casts are temporary until memcached reload
- self::$typeIDs[$typeOrTypeID] = (int) $typeID;
- return (int) $typeID;
- }
-
- $sql = "(SELECT itemTypeID FROM itemTypes WHERE itemTypeID=?) UNION
- (SELECT itemTypeID FROM itemTypes WHERE itemTypeName=?) LIMIT 1";
- $typeID = Zotero_DB::valueQuery($sql, array($typeOrTypeID, $typeOrTypeID));
-
- self::$typeIDs[$typeOrTypeID] = $typeID ? (int) $typeID : false;
- Z_Core::$MC->set($cacheKey, (int) $typeID);
-
- return $typeID ? (int) $typeID : false;
- }
-
-
- public static function getName($typeOrTypeID) {
- if (!$typeOrTypeID) {
- throw new Exception("An item type id or name must be provided");
- }
-
- if (isset(self::$typeNames[$typeOrTypeID])) {
- return self::$typeNames[$typeOrTypeID];
- }
-
- $cacheKey = "itemTypeName_" . $typeOrTypeID;
- $typeName = Z_Core::$MC->get($cacheKey);
- if ($typeName) {
- self::$typeNames[$typeOrTypeID] = $typeName;
- return $typeName;
- }
-
- $sql = "(SELECT itemTypeName FROM itemTypes WHERE itemTypeID=?) UNION
- (SELECT itemTypeName FROM itemTypes WHERE itemTypeName=?) LIMIT 1";
- $typeName = Zotero_DB::valueQuery($sql, array($typeOrTypeID, $typeOrTypeID));
-
- self::$typeNames[$typeOrTypeID] = $typeName;
- Z_Core::$MC->set($cacheKey, $typeName);
-
- return $typeName;
- }
-
-
- public static function getLocalizedString($typeOrTypeID, $locale='en-US') {
- if ($locale != 'en-US') {
- throw new Exception("Locale not yet supported");
- }
-
- $itemType = self::getName($typeOrTypeID);
- return self::$localizedStrings[$itemType];
- }
-
-
- public static function getAll($locale=false) {
- $sql = "SELECT itemTypeID AS id, itemTypeName AS name FROM itemTypes";
- // TEMP - skip nsfReviewer and attachment
- $sql .= " WHERE itemTypeID NOT IN (14,10001)";
- $rows = Zotero_DB::query($sql);
-
- // TODO: cache
-
- if (!$locale) {
- return $rows;
- }
-
- foreach ($rows as &$row) {
- $row['localized'] = self::getLocalizedString($row['id'], $locale);
- }
-
- usort($rows, function ($a, $b) {
- return strcmp($a["localized"], $b["localized"]);
- });
-
- return $rows;
- }
-
-
- public static function getImageSrc($itemType, $linkMode=false, $mimeType=false) {
- $prefix = "/static/i/itemType/treeitem";
-
- if ($itemType == 'attachment') {
- if ($mimeType == 'application/pdf') {
- $itemType .= '-pdf';
- }
- else {
- switch ($linkMode) {
- case 0:
- $itemType .= '-file';
- break;
-
- case 1:
- $itemType .= '-file';
- break;
-
- case 2:
- $itemType .= '-snapshot';
- break;
-
- case 3:
- $itemType .= '-web-link';
- break;
- }
- }
- }
-
- // DEBUG: only have icons for some types so far
- switch ($itemType) {
- case 'attachment-file':
- case 'attachment-link':
- case 'attachment-snapshot':
- case 'attachment-web-link':
- case 'attachment-pdf':
- case 'artwork':
- case 'audioRecording':
- case 'blogPost':
- case 'book':
- case 'bookSection':
- case 'computerProgram':
- case 'conferencePaper':
- case 'email':
- case 'film':
- case 'forumPost':
- case 'interview':
- case 'journalArticle':
- case 'letter':
- case 'magazineArticle':
- case 'manuscript':
- case 'map':
- case 'newspaperArticle':
- case 'note':
- case 'podcast':
- case 'radioBroadcast':
- case 'report':
- case 'thesis':
- case 'tvBroadcast':
- case 'videoRecording':
- case 'webpage':
- return $prefix . '-' . $itemType . ".png";
- }
-
- return $prefix . ".png";
- }
-
-
- public static function isCustomType($itemTypeID) {
- if (isset(self::$customTypeCheck)) {
- return self::$customTypeCheck;
- }
-
- $sql = "SELECT custom FROM itemTypes WHERE itemTypeID=?";
- $isCustom = Zotero_DB::valueQuery($sql, $itemTypeID);
- if ($isCustom === false) {
- throw new Exception("Invalid itemTypeID '$itemTypeID'");
- }
-
- self::$customTypesCheck[$itemTypeID] = !!$isCustom;
-
- return !!$isCustom;
- }
-
-
- public static function addCustomType($name) {
- if (self::getID($name)) {
- throw new Exception("Item type '$name' already exists");
- }
-
- if (!preg_match('/^[a-z][^\s0-9]+$/', $name)) {
- throw new Exception("Invalid item type name '$name'");
- }
-
- // TODO: make sure user hasn't added too many already
-
- throw new Exception("Unimplemented");
- // TODO: add to cache
-
- Zotero_DB::beginTransaction();
-
- $sql = "SELECT NEXT_ID(itemTypeID) FROM itemTypes";
- $itemTypeID = Zotero_DB::valueQuery($sql);
-
- $sql = "INSERT INTO itemTypes (?, ?, ?)";
- Zotero_DB::query($sql, array($itemTypeID, $name, 1));
-
- Zotero_DB::commit();
-
- return $itemTypeID;
- }
-}
-?>
diff --git a/model/Items.inc.php b/model/Items.inc.php
deleted file mode 100644
index cc30995d..00000000
--- a/model/Items.inc.php
+++ /dev/null
@@ -1,2658 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Items {
- use Zotero_DataObjects;
-
- private static $objectType = 'item';
- private static $primaryDataSQLParts = [
- 'id' => 'O.itemID',
- 'libraryID' => 'O.libraryID',
- 'key' => 'O.key',
- 'itemTypeID' => 'O.itemTypeID',
- 'dateAdded' => 'O.dateAdded',
- 'dateModified' => 'O.dateModified',
- 'serverDateModified' => 'O.serverDateModified',
- 'version' => 'O.version'
- ];
-
- public static $maxDataValueLength = 65535;
-
- /**
- *
- * TODO: support limit?
- *
- * @param {Integer[]}
- * @param {Boolean}
- */
- public static function getDeleted($libraryID, $asIDs) {
- $sql = "SELECT itemID FROM deletedItems JOIN items USING (itemID) WHERE libraryID=?";
- $ids = Zotero_DB::columnQuery($sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID));
- if (!$ids) {
- return array();
- }
- if ($asIDs) {
- return $ids;
- }
- return self::get($libraryID, $ids);
- }
-
-
- public static function search($libraryID, $onlyTopLevel=false, $params=array(), $includeTrashed=false, Zotero_Permissions $permissions=null) {
- $rnd = "_" . uniqid($libraryID . "_");
-
- $results = array('results' => array(), 'total' => 0);
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- $isPublications = !empty($params['publications']);
- if ($isPublications && Zotero_Libraries::getType($libraryID) == 'publications') {
- $isPublications = false;
- }
-
- $includeNotes = true;
- if (!$isPublications && $permissions && !$permissions->canAccess($libraryID, 'notes')) {
- $includeNotes = false;
- }
-
- // Pass a list of itemIDs, for when the initial search is done via SQL
- $itemIDs = !empty($params['itemIDs']) ? $params['itemIDs'] : array();
- $itemKeys = $params['itemKey'];
-
- $titleSort = !empty($params['sort']) && $params['sort'] == 'title';
- $parentItemSort = !empty($params['sort'])
- && in_array($params['sort'], ['itemType', 'dateAdded', 'dateModified', 'serverDateModified', 'addedBy']);
-
- $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT ";
-
- // In /top mode, use the parent item's values for most joins
- if ($onlyTopLevel) {
- $itemIDSelector = "COALESCE(IA.sourceItemID, INo.sourceItemID, I.itemID)";
- $itemKeySelector = "COALESCE(IP.key, I.key)";
- $itemVersionSelector = "COALESCE(IP.version, I.version)";
- $itemTypeIDSelector = "COALESCE(IP.itemTypeID, I.itemTypeID)";
- }
- else {
- $itemIDSelector = "I.itemID";
- $itemKeySelector = "I.key";
- $itemVersionSelector = "I.version";
- $itemTypeIDSelector = "I.itemTypeID";
- }
-
- if ($params['format'] == 'keys' || $params['format'] == 'versions') {
- // In /top mode, display the parent item of matching items
- $sql .= "$itemKeySelector AS `key`";
-
- if ($params['format'] == 'versions') {
- $sql .= ", $itemVersionSelector AS version";
- }
- }
- else {
- $sql .= "$itemIDSelector AS itemID";
- }
- $sql .= " FROM items I ";
- $sqlParams = array($libraryID);
-
- // For /top, we need the parent itemID
- if ($onlyTopLevel) {
- $sql .= "LEFT JOIN itemAttachments IA ON (IA.itemID=I.itemID) ";
- }
-
- // For /top, we need the parent itemID; for 'q' we need the note; for sorting by title,
- // we need the note title
- if ($onlyTopLevel || !empty($params['q']) || $titleSort) {
- $sql .= "LEFT JOIN itemNotes INo ON (INo.itemID=I.itemID) ";
- }
-
- // For some /top requests, pull in the parent item's items row
- if ($onlyTopLevel && ($params['format'] == 'keys' || $params['format'] == 'versions' || $parentItemSort)) {
- $sql .= "LEFT JOIN items IP ON ($itemIDSelector=IP.itemID) ";
- }
-
- // Pull in titles
- if (!empty($params['q']) || $titleSort) {
- $titleFieldIDs = array_merge(
- array(Zotero_ItemFields::getID('title')),
- Zotero_ItemFields::getTypeFieldsFromBase('title')
- );
- $sql .= "LEFT JOIN itemData IDT ON (IDT.itemID=I.itemID AND IDT.fieldID IN "
- . "(" . implode(',', $titleFieldIDs) . ")) ";
- }
-
- // When sorting by title in /top mode, we need the title of the parent item
- if ($onlyTopLevel && $titleSort) {
- $titleSortDataTable = "IDTSort";
- $titleSortNoteTable = "INoSort";
- $sql .= "LEFT JOIN itemData IDTSort ON (IDTSort.itemID=$itemIDSelector AND "
- . "IDTSort.fieldID IN (" . implode(',', $titleFieldIDs) . ")) "
- . "LEFT JOIN itemNotes INoSort ON (INoSort.itemID=$itemIDSelector) ";
- }
- else {
- $titleSortDataTable = "IDT";
- $titleSortNoteTable = "INo";
- }
-
- if (!empty($params['q'])) {
- // Pull in creators
- $sql .= "LEFT JOIN itemCreators IC ON (IC.itemID=I.itemID) "
- . "LEFT JOIN creators C ON (C.creatorID=IC.creatorID) ";
-
- // Pull in dates
- $dateFieldIDs = array_merge(
- array(Zotero_ItemFields::getID('date')),
- Zotero_ItemFields::getTypeFieldsFromBase('date')
- );
- $sql .= "LEFT JOIN itemData IDD ON (IDD.itemID=I.itemID AND IDD.fieldID IN "
- . "(" . implode(',', $dateFieldIDs) . ")) ";
- }
-
- if ($includeTrashed) {
- if (!empty($params['trashedItemsOnly'])) {
- $sql .= "JOIN deletedItems DI ON (DI.itemID=I.itemID) ";
- }
- }
- else {
- $sql .= "LEFT JOIN deletedItems DI ON (DI.itemID=I.itemID) ";
-
- // In /top mode, we don't want to show results for deleted parents or children
- if ($onlyTopLevel) {
- $sql .= "LEFT JOIN deletedItems DIP ON (DIP.itemID=$itemIDSelector) ";
- }
- }
-
- if ($isPublications) {
- $sql .= "LEFT JOIN publicationsItems PI ON (PI.itemID=I.itemID) ";
- }
-
- if (!empty($params['sort'])) {
- switch ($params['sort']) {
- case 'title':
- case 'creator':
- $sql .= "LEFT JOIN itemSortFields ISF ON (ISF.itemID=$itemIDSelector) ";
- break;
-
- case 'date':
- // When sorting by date in /top mode, we need the date of the parent item
- if ($onlyTopLevel) {
- $sortTable = "IDDSort";
- // Pull in dates
- $dateFieldIDs = array_merge(
- array(Zotero_ItemFields::getID('date')),
- Zotero_ItemFields::getTypeFieldsFromBase('date')
- );
- $sql .= "LEFT JOIN itemData IDDSort ON (IDDSort.itemID=$itemIDSelector AND "
- . "IDDSort.fieldID IN (" . implode(',', $dateFieldIDs) . ")) ";
- }
- // If we didn't already pull in dates for a quick search, pull in here
- else {
- $sortTable = "IDD";
- if (empty($params['q'])) {
- $dateFieldIDs = array_merge(
- array(Zotero_ItemFields::getID('date')),
- Zotero_ItemFields::getTypeFieldsFromBase('date')
- );
- $sql .= "LEFT JOIN itemData IDD ON (IDD.itemID=I.itemID AND IDD.fieldID IN ("
- . implode(',', $dateFieldIDs) . ")) ";
- }
- }
- break;
-
- case 'itemType':
- $locale = 'en-US';
- $types = Zotero_ItemTypes::getAll($locale);
- // TEMP: get localized string
- // DEBUG: Why is attachment skipped in getAll()?
- $types[] = array(
- 'id' => 14,
- 'localized' => 'Attachment'
- );
- foreach ($types as $type) {
- $sql2 = "INSERT IGNORE INTO tmpItemTypeNames VALUES (?, ?, ?)";
- Zotero_DB::query(
- $sql2,
- array(
- $type['id'],
- $locale,
- $type['localized']
- ),
- $shardID
- );
- }
-
- // Join temp table to query
- $sql .= "JOIN tmpItemTypeNames TITN ON (TITN.itemTypeID=$itemTypeIDSelector) ";
- break;
-
- case 'addedBy':
- $isGroup = Zotero_Libraries::getType($libraryID) == 'group';
- if ($isGroup) {
- $sql2 = "SELECT DISTINCT createdByUserID FROM items
- JOIN groupItems USING (itemID) WHERE
- createdByUserID IS NOT NULL AND ";
- if ($itemIDs) {
- $sql2 .= "itemID IN ("
- . implode(', ', array_fill(0, sizeOf($itemIDs), '?'))
- . ") ";
- $createdByUserIDs = Zotero_DB::columnQuery($sql2, $itemIDs, $shardID);
- }
- else {
- $sql2 .= "libraryID=?";
- $createdByUserIDs = Zotero_DB::columnQuery($sql2, $libraryID, $shardID);
- }
-
- // Populate temp table with usernames
- if ($createdByUserIDs) {
- $toAdd = array();
- foreach ($createdByUserIDs as $createdByUserID) {
- $toAdd[] = array(
- $createdByUserID,
- Zotero_Users::getUsername($createdByUserID)
- );
- }
-
- $sql2 = "INSERT IGNORE INTO tmpCreatedByUsers VALUES ";
- Zotero_DB::bulkInsert($sql2, $toAdd, 50, false, $shardID);
-
- // Join temp table to query
- $sql .= "LEFT JOIN groupItems GI ON (GI.itemID=I.itemID)
- LEFT JOIN tmpCreatedByUsers TCBU ON (TCBU.userID=GI.createdByUserID) ";
- }
- }
- break;
- }
- }
-
- $sql .= "WHERE I.libraryID=? ";
-
- if (!$includeTrashed) {
- $sql .= "AND DI.itemID IS NULL ";
-
- // Hide deleted parents in /top mode
- if ($onlyTopLevel) {
- $sql .= "AND DIP.itemID IS NULL ";
- }
- }
-
- if ($isPublications) {
- $sql .= "AND PI.itemID IS NOT NULL ";
- }
-
- // Search on title, creators, and dates
- if (!empty($params['q'])) {
- $sql .= "AND (";
-
- $sql .= "IDT.value LIKE ? ";
- $sqlParams[] = '%' . $params['q'] . '%';
-
- $sql .= "OR INo.title LIKE ? ";
- $sqlParams[] = '%' . $params['q'] . '%';
-
- $sql .= "OR TRIM(CONCAT(firstName, ' ', lastName)) LIKE ? ";
- $sqlParams[] = '%' . $params['q'] . '%';
-
- $sql .= "OR SUBSTR(IDD.value, 1, 4) = ?";
- $sqlParams[] = $params['q'];
-
- // Full-text search
- if ($params['qmode'] == 'everything') {
- $ftKeys = Zotero_FullText::searchInLibrary($libraryID, $params['q']);
- if ($ftKeys) {
- $sql .= " OR I.key IN ("
- . implode(', ', array_fill(0, sizeOf($ftKeys), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $ftKeys);
- }
- }
-
- $sql .= ") ";
- }
-
- // Search on itemType
- if (!empty($params['itemType'])) {
- $itemTypes = Zotero_API::getSearchParamValues($params, 'itemType');
- if ($itemTypes) {
- if (sizeOf($itemTypes) > 1) {
- throw new Exception("Cannot specify 'itemType' more than once", Z_ERROR_INVALID_INPUT);
- }
- $itemTypes = $itemTypes[0];
-
- $itemTypeIDs = array();
- foreach ($itemTypes['values'] as $itemType) {
- $itemTypeID = Zotero_ItemTypes::getID($itemType);
- if (!$itemTypeID) {
- throw new Exception("Invalid itemType '{$itemType}'", Z_ERROR_INVALID_INPUT);
- }
- $itemTypeIDs[] = $itemTypeID;
- }
-
- $sql .= "AND I.itemTypeID " . ($itemTypes['negation'] ? "NOT " : "") . "IN ("
- . implode(',', array_fill(0, sizeOf($itemTypeIDs), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $itemTypeIDs);
- }
- }
-
- if (!$includeNotes) {
- $sql .= "AND I.itemTypeID != 1 ";
- }
-
- if (!empty($params['since'])) {
- $sql .= "AND $itemVersionSelector > ? ";
- $sqlParams[] = $params['since'];
- }
-
- // TEMP: for sync transition
- if (!empty($params['sincetime']) && $params['sincetime'] != 1) {
- $sql .= "AND I.serverDateModified >= FROM_UNIXTIME(?) ";
- $sqlParams[] = $params['sincetime'];
- }
-
- // Tags
- //
- // ?tag=foo
- // ?tag=foo bar // phrase
- // ?tag=-foo // negation
- // ?tag=\-foo // literal hyphen (only for first character)
- // ?tag=foo&tag=bar // AND
- $tagSets = Zotero_API::getSearchParamValues($params, 'tag');
-
- if ($tagSets) {
- $sql2 = "SELECT itemID FROM items WHERE libraryID=?\n";
- $sqlParams2 = array($libraryID);
-
- $positives = array();
- $negatives = array();
-
- foreach ($tagSets as $set) {
- $tagIDs = array();
-
- foreach ($set['values'] as $tag) {
- $ids = Zotero_Tags::getIDs($libraryID, $tag, true);
- if (!$ids) {
- $ids = array(0);
- }
- $tagIDs = array_merge($tagIDs, $ids);
- }
-
- $tagIDs = array_unique($tagIDs);
-
- $tmpSQL = "SELECT itemID FROM items JOIN itemTags USING (itemID) "
- . "WHERE tagID IN (" . implode(',', array_fill(0, sizeOf($tagIDs), '?')) . ")";
- $ids = Zotero_DB::columnQuery($tmpSQL, $tagIDs, $shardID);
-
- if (!$ids) {
- // If no negative tags, skip this tag set
- if ($set['negation']) {
- continue;
- }
-
- // If no positive tags, return no matches
- return $results;
- }
-
- $ids = $ids ? $ids : array();
- $sql2 .= " AND itemID " . ($set['negation'] ? "NOT " : "") . " IN ("
- . implode(',', array_fill(0, sizeOf($ids), '?')) . ")";
- $sqlParams2 = array_merge($sqlParams2, $ids);
- }
-
- $tagItems = Zotero_DB::columnQuery($sql2, $sqlParams2, $shardID);
-
- // No matches
- if (!$tagItems) {
- return $results;
- }
-
- // Combine with passed ids
- if ($itemIDs) {
- $itemIDs = array_intersect($itemIDs, $tagItems);
- // None of the tag matches match the passed ids
- if (!$itemIDs) {
- return $results;
- }
- }
- else {
- $itemIDs = $tagItems;
- }
- }
-
- if ($itemIDs) {
- $sql .= "AND $itemIDSelector IN ("
- . implode(', ', array_fill(0, sizeOf($itemIDs), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $itemIDs);
- }
-
- if ($itemKeys) {
- $sql .= "AND I.key IN ("
- . implode(', ', array_fill(0, sizeOf($itemKeys), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $itemKeys);
- }
-
- $sql .= "ORDER BY ";
-
- if (!empty($params['sort'])) {
- switch ($params['sort']) {
- case 'dateAdded':
- case 'dateModified':
- case 'serverDateModified':
- if ($onlyTopLevel) {
- $orderSQL = "IP." . $params['sort'];
- }
- else {
- $orderSQL = "I." . $params['sort'];
- }
- break;
-
-
- case 'itemType';
- $orderSQL = "TITN.itemTypeName";
- /*
- // Optional method for sorting by localized item type name, which would avoid
- // the INSERT and JOIN above and allow these requests to use DB read replicas
- $locale = 'en-US';
- $types = Zotero_ItemTypes::getAll($locale);
- // TEMP: get localized string
- // DEBUG: Why is attachment skipped in getAll()?
- $types[] = [
- 'id' => 14,
- 'localized' => 'Attachment'
- ];
- usort($types, function ($a, $b) {
- return strcasecmp($a['localized'], $b['localized']);
- });
- // Pass order of localized item type names for sorting
- // e.g., FIELD(14, 12, 14, 26...) for sorting "Attachment" after "Artwork"
- $orderSQL = "FIELD($itemTypeIDSelector, "
- . implode(", ", array_map(function ($x) {
- return $x['id'];
- }, $types)) . ")";
- // If itemTypeID isn't found in passed list (currently only for NSF Reviewer),
- // sort last
- $orderSQL = "IFNULL(NULLIF($orderSQL, 0), 99999)";
- // All items have types, so no need to check for empty sort values
- $params['emptyFirst'] = true;
- */
- break;
-
- case 'title':
- $orderSQL = "IFNULL(COALESCE(sortTitle, $titleSortDataTable.value, $titleSortNoteTable.title), '')";
- break;
-
- case 'creator':
- $orderSQL = "ISF.creatorSummary";
- break;
-
- // TODO: generic base field mapping-aware sorting
- case 'date':
- $orderSQL = "$sortTable.value";
- break;
-
- case 'addedBy':
- if ($isGroup && $createdByUserIDs) {
- $orderSQL = "TCBU.username";
- }
- else {
- $orderSQL = ($onlyTopLevel ? "IP" : "I") . ".dateAdded";
- }
- break;
-
- case 'itemKeyList':
- $orderSQL = "FIELD(I.key,"
- . implode(',', array_fill(0, sizeOf($itemKeys), '?')) . ")";
- $sqlParams = array_merge($sqlParams, $itemKeys);
- break;
-
- default:
- $fieldID = Zotero_ItemFields::getID($params['sort']);
- if (!$fieldID) {
- throw new Exception("Invalid order field '" . $params['sort'] . "'");
- }
- $orderSQL = "(SELECT value FROM itemData WHERE itemID=I.itemID AND fieldID=?)";
- if (!$params['emptyFirst']) {
- $sqlParams[] = $fieldID;
- }
- $sqlParams[] = $fieldID;
- }
-
- if (!empty($params['direction'])) {
- $dir = $params['direction'];
- }
- else {
- $dir = "ASC";
- }
-
- if (!$params['emptyFirst']) {
- $sql .= "IFNULL($orderSQL, '') = '' $dir, ";
- }
-
- $sql .= $orderSQL . " $dir, ";
- }
- $sql .= "I.version " . (!empty($params['direction']) ? $params['direction'] : "ASC")
- . ", I.itemID " . (!empty($params['direction']) ? $params['direction'] : "ASC") . " ";
-
- if (!empty($params['limit'])) {
- $sql .= "LIMIT ?, ?";
- $sqlParams[] = $params['start'] ? $params['start'] : 0;
- $sqlParams[] = $params['limit'];
- }
-
- // Log SQL statement with embedded parameters
- /*if (true || !empty($_GET['sqldebug'])) {
- error_log($onlyTopLevel);
-
- $debugSQL = "";
- $parts = explode("?", $sql);
- $debugSQLParams = $sqlParams;
- foreach ($parts as $part) {
- $val = array_shift($debugSQLParams);
- $debugSQL .= $part;
- if (!is_null($val)) {
- $debugSQL .= is_int($val) ? $val : '"' . $val . '"';
- }
- }
- error_log($debugSQL . ";");
- }*/
-
- if ($params['format'] == 'versions') {
- $rows = Zotero_DB::query($sql, $sqlParams, $shardID);
- }
- // keys and ids
- else {
- $rows = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
- }
-
- $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
- if ($rows) {
- if ($params['format'] == 'keys') {
- $results['results'] = $rows;
- }
- else if ($params['format'] == 'versions') {
- foreach ($rows as $row) {
- $results['results'][$row['key']] = $row['version'];
- }
- }
- else {
- $results['results'] = Zotero_Items::get($libraryID, $rows);
- }
- }
-
- return $results;
- }
-
-
- /**
- * Store item in internal id-based cache
- */
- public static function cache(Zotero_Item $item) {
- if (isset(self::$objectCache[$item->id])) {
- Z_Core::debug("Item $item->id is already cached");
- }
-
- self::$itemsByID[$item->id] = $item;
- }
-
-
- public static function updateVersions($items, $userID=false) {
- $libraryShards = array();
- $libraryIsGroup = array();
- $shardItemIDs = array();
- $shardGroupItemIDs = array();
- $libraryItems = array();
-
- foreach ($items as $item) {
- $libraryID = $item->libraryID;
- $itemID = $item->id;
-
- // Index items by shard
- if (isset($libraryShards[$libraryID])) {
- $shardID = $libraryShards[$libraryID];
- $shardItemIDs[$shardID][] = $itemID;
- }
- else {
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- $libraryShards[$libraryID] = $shardID;
- $shardItemIDs[$shardID] = array($itemID);
- }
-
- // Separate out group items by shard
- if (!isset($libraryIsGroup[$libraryID])) {
- $libraryIsGroup[$libraryID] =
- Zotero_Libraries::getType($libraryID) == 'group';
- }
- if ($libraryIsGroup[$libraryID]) {
- if (isset($shardGroupItemIDs[$shardID])) {
- $shardGroupItemIDs[$shardID][] = $itemID;
- }
- else {
- $shardGroupItemIDs[$shardID] = array($itemID);
- }
- }
-
- // Index items by library
- if (!isset($libraryItems[$libraryID])) {
- $libraryItems[$libraryID] = array();
- }
- $libraryItems[$libraryID][] = $item;
- }
-
- Zotero_DB::beginTransaction();
- foreach ($shardItemIDs as $shardID => $itemIDs) {
- // Group item data
- if ($userID && isset($shardGroupItemIDs[$shardID])) {
- $sql = "UPDATE groupItems SET lastModifiedByUserID=? "
- . "WHERE itemID IN ("
- . implode(',', array_fill(0, sizeOf($shardGroupItemIDs[$shardID]), '?')) . ")";
- Zotero_DB::query(
- $sql,
- array_merge(array($userID), $shardGroupItemIDs[$shardID]),
- $shardID
- );
- }
- }
- foreach ($libraryItems as $libraryID => $items) {
- $itemIDs = array();
- foreach ($items as $item) {
- $itemIDs[] = $item->id;
- }
- $version = Zotero_Libraries::getUpdatedVersion($libraryID);
- $sql = "UPDATE items SET version=? WHERE itemID IN "
- . "(" . implode(',', array_fill(0, sizeOf($itemIDs), '?')) . ")";
- Zotero_DB::query($sql, array_merge(array($version), $itemIDs), $shardID);
- }
- Zotero_DB::commit();
-
- foreach ($libraryItems as $libraryID => $items) {
- foreach ($items as $item) {
- $item->reload();
- }
-
- $libraryKeys = array_map(function ($item) use ($libraryID) {
- return $libraryID . "/" . $item->key;
- }, $items);
-
- Zotero_Notifier::trigger('modify', 'item', $libraryKeys);
- }
- }
-
-
- public static function getDataValuesFromXML(DOMDocument $doc) {
- $xpath = new DOMXPath($doc);
- $fields = $xpath->evaluate('//items/item/field');
- $vals = array();
- foreach ($fields as $f) {
- $vals[] = $f->firstChild->nodeValue;
- }
- $vals = array_unique($vals);
- return $vals;
- }
-
-
- public static function getLongDataValueFromXML(DOMDocument $doc) {
- $xpath = new DOMXPath($doc);
- $fields = $xpath->evaluate('//items/item/field[string-length(text()) > ' . self::$maxDataValueLength . ']');
- return $fields->length ? $fields->item(0) : false;
- }
-
-
- /**
- * Converts a DOMElement item to a Zotero_Item object
- *
- * @param DOMElement $xml Item data as DOMElement
- * @return Zotero_Item Zotero item object
- */
- public static function convertXMLToItem(DOMElement $xml, $skipCreators = []) {
- // Get item type id, adding custom type if necessary
- $itemTypeName = $xml->getAttribute('itemType');
- $itemTypeID = Zotero_ItemTypes::getID($itemTypeName);
- if (!$itemTypeID) {
- $itemTypeID = Zotero_ItemTypes::addCustomType($itemTypeName);
- }
-
- // Primary fields
- $libraryID = (int) $xml->getAttribute('libraryID');
- $itemObj = self::getByLibraryAndKey($libraryID, $xml->getAttribute('key'));
- if (!$itemObj) {
- $itemObj = new Zotero_Item;
- $itemObj->libraryID = $libraryID;
- $itemObj->key = $xml->getAttribute('key');
- }
- $itemObj->setField('itemTypeID', $itemTypeID, false, true);
- $itemObj->setField('dateAdded', $xml->getAttribute('dateAdded'), false, true);
- $itemObj->setField('dateModified', $xml->getAttribute('dateModified'), false, true);
-
- $xmlFields = array();
- $xmlCreators = array();
- $xmlNote = null;
- $xmlPath = null;
- $xmlRelated = null;
- $childNodes = $xml->childNodes;
- foreach ($childNodes as $child) {
- switch ($child->nodeName) {
- case 'field':
- $xmlFields[] = $child;
- break;
-
- case 'creator':
- $xmlCreators[] = $child;
- break;
-
- case 'note':
- $xmlNote = $child;
- break;
-
- case 'path':
- $xmlPath = $child;
- break;
-
- case 'related':
- $xmlRelated = $child;
- break;
- }
- }
-
- // Item data
- $setFields = array();
- foreach ($xmlFields as $field) {
- // TODO: add custom fields
-
- $fieldName = $field->getAttribute('name');
- // Special handling for renamed computerProgram 'version' field
- if ($itemTypeID == 32 && $fieldName == 'version') {
- $fieldName = 'versionNumber';
- }
- $itemObj->setField($fieldName, $field->nodeValue, false, true);
- $setFields[$fieldName] = true;
- }
- $previousFields = $itemObj->getUsedFields(true);
-
- foreach ($previousFields as $field) {
- if (!isset($setFields[$field])) {
- $itemObj->setField($field, false, false, true);
- }
- }
-
- $deleted = $xml->getAttribute('deleted');
- $itemObj->deleted = ($deleted == 'true' || $deleted == '1');
-
- // Creators
- $i = 0;
- foreach ($xmlCreators as $creator) {
- // TODO: add custom creator types
-
- $key = $creator->getAttribute('key');
- $creatorObj = Zotero_Creators::getByLibraryAndKey($libraryID, $key);
- // If creator doesn't exist locally (e.g., if it was deleted locally
- // and appears in a new/modified item remotely), get it from within
- // the item's creator block, where a copy should be provided
- if (!$creatorObj) {
- $subcreator = $creator->getElementsByTagName('creator')->item(0);
- if (!$subcreator) {
- if (!empty($skipCreators[$libraryID]) && in_array($key, $skipCreators[$libraryID])) {
- error_log("Skipping empty referenced creator $key for item $libraryID/$itemObj->key");
- continue;
- }
- throw new Exception("Data for missing local creator $key not provided", Z_ERROR_CREATOR_NOT_FOUND);
- }
- $creatorObj = Zotero_Creators::convertXMLToCreator($subcreator, $libraryID);
- if ($creatorObj->key != $key) {
- throw new Exception("Creator key " . $creatorObj->key .
- " does not match item creator key $key");
- }
- }
- if (Zotero_Utilities::unicodeTrim($creatorObj->firstName) === ''
- && Zotero_Utilities::unicodeTrim($creatorObj->lastName) === '') {
- continue;
- }
- $creatorTypeID = Zotero_CreatorTypes::getID($creator->getAttribute('creatorType'));
- $itemObj->setCreator($i, $creatorObj, $creatorTypeID);
- $i++;
- }
-
- // Remove item's remaining creators not in XML
- $numCreators = $itemObj->numCreators();
- $rem = $numCreators - $i;
- for ($j=0; $j<$rem; $j++) {
- // Keep removing last creator
- $itemObj->removeCreator($i);
- }
-
- // Both notes and attachments might have parents and notes
- if ($itemTypeName == 'note' || $itemTypeName == 'attachment') {
- $sourceItemKey = $xml->getAttribute('sourceItem');
- $itemObj->setSource($sourceItemKey ? $sourceItemKey : false);
- $itemObj->setNote($xmlNote ? $xmlNote->nodeValue : "");
- }
-
- // Attachment metadata
- if ($itemTypeName == 'attachment') {
- $itemObj->attachmentLinkMode = (int) $xml->getAttribute('linkMode');
- $itemObj->attachmentMIMEType = $xml->getAttribute('mimeType');
- $itemObj->attachmentCharset = $xml->getAttribute('charset');
- // Cast to string to be 32-bit safe
- $storageModTime = (string) $xml->getAttribute('storageModTime');
- $itemObj->attachmentStorageModTime = $storageModTime ? $storageModTime : null;
- $storageHash = $xml->getAttribute('storageHash');
- $itemObj->attachmentStorageHash = $storageHash ? $storageHash : null;
- $itemObj->attachmentPath = $xmlPath ? $xmlPath->nodeValue : "";
- }
-
- // Related items
- if ($xmlRelated && $xmlRelated->nodeValue) {
- $relatedKeys = explode(' ', $xmlRelated->nodeValue);
- }
- else {
- $relatedKeys = array();
- }
- $itemObj->relatedItems = $relatedKeys;
-
- return $itemObj;
- }
-
-
- /**
- * Converts a Zotero_Item object to a SimpleXMLElement item
- *
- * @param object $item Zotero_Item object
- * @param array $data
- * @return SimpleXMLElement Item data as SimpleXML element
- */
- public static function convertItemToXML(Zotero_Item $item, $data=array()) {
- $t = microtime(true);
-
- // Check cache for all items except imported attachments,
- // which don't have their versions updated when the client
- // updates their file metadata
- if (!$item->isImportedAttachment()) {
- $cacheVersion = 1;
- $cacheKey = "syncXMLItem_" . $item->libraryID . "/" . $item->id . "_"
- . $item->version
- . "_" . md5(json_encode($data))
- // For code-based changes
- . "_" . $cacheVersion
- // For data-based changes
- . (isset(Z_CONFIG::$CACHE_VERSION_SYNC_XML_ITEM)
- ? "_" . Z_CONFIG::$CACHE_VERSION_SYNC_XML_ITEM
- : "");
- $xmlstr = Z_Core::$MC->get($cacheKey);
- }
- else {
- $cacheKey = false;
- $xmlstr = false;
- }
- if ($xmlstr) {
- $xml = new SimpleXMLElement($xmlstr);
-
- StatsD::timing("api.items.itemToSyncXML.cached", (microtime(true) - $t) * 1000);
- StatsD::increment("memcached.items.itemToSyncXML.hit");
-
- // Skip the cache every 10 times for now, to ensure cache sanity
- if (Z_Core::probability(10)) {
- //$xmlstr = $xml->saveXML();
- }
- else {
- Z_Core::debug("Using cached sync XML item");
- return $xml;
- }
- }
-
- $xml = new SimpleXMLElement(' ');
-
- // Primary fields
- foreach (self::$primaryFields as $field) {
- switch ($field) {
- case 'id':
- case 'serverDateModified':
- case 'version':
- continue (2);
-
- case 'itemTypeID':
- $xmlField = 'itemType';
- $xmlValue = Zotero_ItemTypes::getName($item->$field);
- break;
-
- default:
- $xmlField = $field;
- $xmlValue = $item->$field;
- }
-
- $xml[$xmlField] = $xmlValue;
- }
-
- // Item data
- $itemTypeID = $item->itemTypeID;
- $fieldIDs = $item->getUsedFields();
- foreach ($fieldIDs as $fieldID) {
- $val = $item->getField($fieldID);
- if ($val == '') {
- continue;
- }
- $f = $xml->addChild('field', htmlspecialchars($val));
- $fieldName = Zotero_ItemFields::getName($fieldID);
- // Special handling for renamed computerProgram 'version' field
- if ($itemTypeID == 32 && $fieldName == 'versionNumber') {
- $fieldName = 'version';
- }
- $f['name'] = htmlspecialchars($fieldName);
- }
-
- // Deleted item flag
- if ($item->deleted) {
- $xml['deleted'] = '1';
- }
-
- if ($item->isNote() || $item->isAttachment()) {
- $sourceItemID = $item->getSource();
- if ($sourceItemID) {
- $sourceItem = Zotero_Items::get($item->libraryID, $sourceItemID);
- if (!$sourceItem) {
- throw new Exception("Parent item $sourceItemID not found");
- }
- $xml['sourceItem'] = $sourceItem->key;
- }
- }
-
- // Group modification info
- $createdByUserID = null;
- $lastModifiedByUserID = null;
- switch (Zotero_Libraries::getType($item->libraryID)) {
- case 'group':
- $createdByUserID = $item->createdByUserID;
- $lastModifiedByUserID = $item->lastModifiedByUserID;
- break;
- }
- if ($createdByUserID) {
- $xml['createdByUserID'] = $createdByUserID;
- }
- if ($lastModifiedByUserID) {
- $xml['lastModifiedByUserID'] = $lastModifiedByUserID;
- }
-
- if ($item->isAttachment()) {
- $linkMode = $item->attachmentLinkMode;
- $xml['linkMode'] = Zotero_Attachments::linkModeNameToNumber($linkMode);
- $xml['mimeType'] = $item->attachmentMIMEType;
- if ($item->attachmentCharset) {
- $xml['charset'] = $item->attachmentCharset;
- }
-
- $storageModTime = $item->attachmentStorageModTime;
- if ($storageModTime) {
- $xml['storageModTime'] = $storageModTime;
- }
-
- $storageHash = $item->attachmentStorageHash;
- if ($storageHash) {
- $xml['storageHash'] = $storageHash;
- }
-
- if ($linkMode != 'linked_url') {
- $xml->addChild('path', htmlspecialchars($item->attachmentPath));
- }
- }
-
- // Note
- if ($item->isNote() || $item->isAttachment()) {
- // Get htmlspecialchars'ed note
- $note = $item->getNote(false, true);
- if ($note !== '') {
- $xml->addChild('note', $note);
- }
- else if ($item->isNote()) {
- $xml->addChild('note', '');
- }
- }
-
- // Creators
- $creators = $item->getCreators();
- if ($creators) {
- foreach ($creators as $index => $creator) {
- $c = $xml->addChild('creator');
- $c['key'] = $creator['ref']->key;
- $c['creatorType'] = htmlspecialchars(
- Zotero_CreatorTypes::getName($creator['creatorTypeID'])
- );
- $c['index'] = $index;
- if (empty($data['updatedCreators']) ||
- !in_array($creator['ref']->id, $data['updatedCreators'])) {
- $cNode = dom_import_simplexml($c);
- $creatorXML = Zotero_Creators::convertCreatorToXML($creator['ref'], $cNode->ownerDocument);
- $cNode->appendChild($creatorXML);
- }
- }
- }
-
- // Related items
- $relatedKeys = $item->relatedItems;
- $keys = array();
- foreach ($relatedKeys as $relatedKey) {
- if (Zotero_Items::getByLibraryAndKey($item->libraryID, $relatedKey)) {
- $keys[] = $relatedKey;
- }
- }
- if ($keys) {
- $xml->related = implode(' ', $keys);
- }
-
- if ($xmlstr) {
- $uncached = $xml->saveXML();
- if ($xmlstr != $uncached) {
- error_log("Cached sync XML item does not match");
- error_log(" Cached: " . $xmlstr);
- error_log("Uncached: " . $uncached);
- }
- }
- else {
- $xmlstr = $xml->saveXML();
- if ($cacheKey) {
- Z_Core::$MC->set($cacheKey, $xmlstr, 3600); // 1 hour for now
- }
- StatsD::timing("api.items.itemToSyncXML.uncached", (microtime(true) - $t) * 1000);
- StatsD::increment("memcached.items.itemToSyncXML.miss");
- }
-
- return $xml;
- }
-
-
- /**
- * Converts a Zotero_Item object to a SimpleXMLElement Atom object
- *
- * Note: Increment Z_CONFIG::$CACHE_VERSION_ATOM_ENTRY when changing
- * the response.
- *
- * @param object $item Zotero_Item object
- * @param string $content
- * @return SimpleXMLElement Item data as SimpleXML element
- */
- public static function convertItemToAtom(Zotero_Item $item, $queryParams, $permissions, $sharedData=null) {
- $t = microtime(true);
-
- // Uncached stuff or parts of the cache key
- $version = $item->version;
- $parent = $item->getSource();
- $isRegularItem = !$parent && $item->isRegularItem();
-
- $props = $item->getUncachedResponseProps($queryParams, $permissions);
- $downloadDetails = $props['downloadDetails'];
- $numChildren = $props['numChildren'];
-
- // changes based on group visibility in v1
- if ($queryParams['v'] < 2) {
- $id = Zotero_URI::getItemURI($item, false, true);
- }
- else {
- $id = Zotero_URI::getItemURI($item);
- }
- $libraryType = Zotero_Libraries::getType($item->libraryID);
-
- // Any query parameters that have an effect on the output
- // need to be added here
- $allowedParams = array(
- 'content',
- 'style',
- 'css',
- 'linkwrap',
- 'publications'
- );
- $cachedParams = Z_Array::filterKeys($queryParams, $allowedParams);
-
- $cacheVersion = 3;
- $cacheKey = "atomEntry_" . $item->libraryID . "/" . $item->id . "_"
- . md5(
- $version
- . json_encode($cachedParams)
- . ($downloadDetails ? 'hasFile' : '')
- . ($libraryType == 'group' ? 'id' . $id : '')
- )
- . "_" . $queryParams['v']
- // For code-based changes
- . "_" . $cacheVersion
- // For data-based changes
- . (isset(Z_CONFIG::$CACHE_VERSION_ATOM_ENTRY)
- ? "_" . Z_CONFIG::$CACHE_VERSION_ATOM_ENTRY
- : "")
- // If there's bib content, include the bib cache version
- . ((in_array('bib', $queryParams['content'])
- && isset(Z_CONFIG::$CACHE_VERSION_BIB))
- ? "_" . Z_CONFIG::$CACHE_VERSION_BIB
- : "");
-
- $xmlstr = Z_Core::$MC->get($cacheKey);
- if ($xmlstr) {
- try {
- // TEMP: Strip control characters
- $xmlstr = Zotero_Utilities::cleanString($xmlstr, true);
-
- $doc = new DOMDocument;
- $doc->loadXML($xmlstr);
- $xpath = new DOMXpath($doc);
- $xpath->registerNamespace('atom', Zotero_Atom::$nsAtom);
- $xpath->registerNamespace('zapi', Zotero_Atom::$nsZoteroAPI);
- $xpath->registerNamespace('xhtml', Zotero_Atom::$nsXHTML);
-
- // Make sure numChildren reflects the current permissions
- if ($isRegularItem) {
- $xpath->query('/atom:entry/zapi:numChildren')
- ->item(0)->nodeValue = $numChildren;
- }
-
- // To prevent PHP from messing with namespace declarations,
- // we have to extract, remove, and then add back
- // subelements. Otherwise the subelements become, say,
- // instead
- // of just , and
- // xmlns:default="http://www.w3.org/1999/xhtml" gets added to
- // the parent . While you might reasonably think that
- //
- // echo $xml->saveXML();
- //
- // and
- //
- // $xml = new SimpleXMLElement($xml->saveXML());
- // echo $xml->saveXML();
- //
- // would be identical, you would be wrong.
- $multiFormat = !!$xpath
- ->query('/atom:entry/atom:content/zapi:subcontent')
- ->length;
-
- $contentNodes = array();
- if ($multiFormat) {
- $contentNodes = $xpath->query('/atom:entry/atom:content/zapi:subcontent');
- }
- else {
- $contentNodes = $xpath->query('/atom:entry/atom:content');
- }
-
- foreach ($contentNodes as $contentNode) {
- $contentParts = array();
- while ($contentNode->hasChildNodes()) {
- $contentParts[] = $doc->saveXML($contentNode->firstChild);
- $contentNode->removeChild($contentNode->firstChild);
- }
-
- foreach ($contentParts as $part) {
- if (!trim($part)) {
- continue;
- }
-
- // Strip the namespace and add it back via SimpleXMLElement,
- // which keeps it from being changed later
- if (preg_match('%^<[^>]+xmlns="http://www.w3.org/1999/xhtml"%', $part)) {
- $part = preg_replace(
- '%^(<[^>]+)xmlns="http://www.w3.org/1999/xhtml"%', '$1', $part
- );
- $html = new SimpleXMLElement($part);
- $html['xmlns'] = "http://www.w3.org/1999/xhtml";
- $subNode = dom_import_simplexml($html);
- $importedNode = $doc->importNode($subNode, true);
- $contentNode->appendChild($importedNode);
- }
- else if (preg_match('%^<[^>]+xmlns="http://zotero.org/ns/transfer"%', $part)) {
- $part = preg_replace(
- '%^(<[^>]+)xmlns="http://zotero.org/ns/transfer"%', '$1', $part
- );
- $html = new SimpleXMLElement($part);
- $html['xmlns'] = "http://zotero.org/ns/transfer";
- $subNode = dom_import_simplexml($html);
- $importedNode = $doc->importNode($subNode, true);
- $contentNode->appendChild($importedNode);
- }
- // Non-XML blocks get added back as-is
- else {
- $docFrag = $doc->createDocumentFragment();
- $docFrag->appendXML($part);
- $contentNode->appendChild($docFrag);
- }
- }
- }
-
- $xml = simplexml_import_dom($doc);
-
- StatsD::timing("api.items.itemToAtom.cached", (microtime(true) - $t) * 1000);
- StatsD::increment("memcached.items.itemToAtom.hit");
-
- // Skip the cache every 10 times for now, to ensure cache sanity
- if (Z_Core::probability(10)) {
- $xmlstr = $xml->saveXML();
- }
- else {
- return $xml;
- }
- }
- catch (Exception $e) {
- error_log($xmlstr);
- error_log("WARNING: " . $e);
- }
- }
-
- $content = $queryParams['content'];
- $contentIsHTML = sizeOf($content) == 1 && $content[0] == 'html';
- $contentParamString = urlencode(implode(',', $content));
- $style = $queryParams['style'];
-
- $entry = ''
- . ' ';
- $xml = new SimpleXMLElement($entry);
-
- $title = $item->getDisplayTitle(true);
- $title = $title ? $title : '[Untitled]';
- $xml->title = $title;
-
- $author = $xml->addChild('author');
- $createdByUserID = null;
- $lastModifiedByUserID = null;
- switch (Zotero_Libraries::getType($item->libraryID)) {
- case 'group':
- $createdByUserID = $item->createdByUserID;
- // Used for zapi:lastModifiedByUser below
- $lastModifiedByUserID = $item->lastModifiedByUserID;
- break;
- }
- if ($createdByUserID) {
- try {
- $author->name = Zotero_Users::getUsername($createdByUserID);
- $author->uri = Zotero_URI::getUserURI($createdByUserID);
- }
- // If user no longer exists, use library for author instead
- catch (Exception $e) {
- if (!Zotero_Users::exists($createdByUserID)) {
- $author->name = Zotero_Libraries::getName($item->libraryID);
- $author->uri = Zotero_URI::getLibraryURI($item->libraryID);
- }
- else {
- throw $e;
- }
- }
- }
- else {
- $author->name = Zotero_Libraries::getName($item->libraryID);
- $author->uri = Zotero_URI::getLibraryURI($item->libraryID);
- }
-
- $xml->id = $id;
-
- $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";
- $href = Zotero_API::getItemURI($item) . "?format=atom";
- if ($queryParams['publications']) {
- $href = str_replace("/items/", "/publications/items/", $href);
- }
- if (!$contentIsHTML) {
- $href .= "&content=$contentParamString";
- }
- $link['href'] = $href;
-
- if ($parent) {
- // TODO: handle group items?
- $parentItem = Zotero_Items::get($item->libraryID, $parent);
- $link = $xml->addChild("link");
- $link['rel'] = "up";
- $link['type'] = "application/atom+xml";
- $href = Zotero_API::getItemURI($parentItem) . "?format=atom";
- if (!$contentIsHTML) {
- $href .= "&content=$contentParamString";
- }
- $link['href'] = $href;
- }
-
- $link = $xml->addChild('link');
- $link['rel'] = 'alternate';
- $link['type'] = 'text/html';
- $link['href'] = Zotero_URI::getItemURI($item, true);
-
- // If appropriate permissions and the file is stored in ZFS, get file request link
- if ($downloadDetails) {
- $details = $downloadDetails;
- $link = $xml->addChild('link');
- $link['rel'] = 'enclosure';
- $type = $item->attachmentMIMEType;
- if ($type) {
- $link['type'] = $type;
- }
- $link['href'] = $details['url'];
- if (!empty($details['filename'])) {
- $link['title'] = $details['filename'];
- }
- if (isset($details['size'])) {
- $link['length'] = $details['size'];
- }
- }
-
- $xml->addChild('zapi:key', $item->key, Zotero_Atom::$nsZoteroAPI);
- $xml->addChild('zapi:version', $item->version, Zotero_Atom::$nsZoteroAPI);
-
- if ($lastModifiedByUserID) {
- try {
- $xml->addChild(
- 'zapi:lastModifiedByUser',
- Zotero_Users::getUsername($lastModifiedByUserID),
- Zotero_Atom::$nsZoteroAPI
- );
- }
- // If user no longer exists, this will fail
- catch (Exception $e) {
- if (Zotero_Users::exists($lastModifiedByUserID)) {
- throw $e;
- }
- }
- }
-
- $xml->addChild(
- 'zapi:itemType',
- Zotero_ItemTypes::getName($item->itemTypeID),
- Zotero_Atom::$nsZoteroAPI
- );
- if ($isRegularItem) {
- $val = $item->creatorSummary;
- if ($val !== '') {
- $xml->addChild(
- 'zapi:creatorSummary',
- htmlspecialchars($val),
- Zotero_Atom::$nsZoteroAPI
- );
- }
-
- $val = $item->getField('date', true, true, true);
- if ($val !== '') {
- // TODO: Make sure all stored values are multipart strings
- if (!Zotero_Date::isMultipart($val)) {
- $val = Zotero_Date::strToMultipart($val);
- }
- if ($queryParams['v'] < 3) {
- $val = substr($val, 0, 4);
- if ($val !== '0000') {
- $xml->addChild('zapi:year', $val, Zotero_Atom::$nsZoteroAPI);
- }
- }
- else {
- $sqlDate = Zotero_Date::multipartToSQL($val);
- if (substr($sqlDate, 0, 4) !== '0000') {
- $xml->addChild(
- 'zapi:parsedDate',
- Zotero_Date::sqlToISO8601($sqlDate),
- Zotero_Atom::$nsZoteroAPI
- );
- }
- }
- }
-
- $xml->addChild(
- 'zapi:numChildren',
- $numChildren,
- Zotero_Atom::$nsZoteroAPI
- );
- }
-
- if ($queryParams['v'] < 3) {
- $xml->addChild(
- 'zapi:numTags',
- $item->numTags(),
- Zotero_Atom::$nsZoteroAPI
- );
- }
-
- $xml->content = '';
-
- //
- // DOM XML from here on out
- //
-
- $contentNode = dom_import_simplexml($xml->content);
- $domDoc = $contentNode->ownerDocument;
- $multiFormat = sizeOf($content) > 1;
-
- // Create a root XML document for multi-format responses
- if ($multiFormat) {
- $contentNode->setAttribute('type', 'application/xml');
- /*$multicontent = $domDoc->createElementNS(
- Zotero_Atom::$nsZoteroAPI, 'multicontent'
- );
- $contentNode->appendChild($multicontent);*/
- }
-
- foreach ($content as $type) {
- // Set the target to either the main
- // or a
- if (!$multiFormat) {
- $target = $contentNode;
- }
- else {
- $target = $domDoc->createElementNS(
- Zotero_Atom::$nsZoteroAPI, 'subcontent'
- );
- $contentNode->appendChild($target);
- }
-
- $target->setAttributeNS(
- Zotero_Atom::$nsZoteroAPI,
- "zapi:type",
- $type
- );
-
- if ($type == 'html') {
- if (!$multiFormat) {
- $target->setAttribute('type', 'xhtml');
- }
- $div = $domDoc->createElementNS(
- Zotero_Atom::$nsXHTML, 'div'
- );
- $target->appendChild($div);
- $html = $item->toHTML(true, $queryParams);
- $subNode = dom_import_simplexml($html);
- $importedNode = $domDoc->importNode($subNode, true);
- $div->appendChild($importedNode);
- }
- else if ($type == 'citation') {
- if (!$multiFormat) {
- $target->setAttribute('type', 'xhtml');
- }
- if (isset($sharedData[$type][$item->libraryID . "/" . $item->key])) {
- $html = $sharedData[$type][$item->libraryID . "/" . $item->key];
- }
- else {
- if ($sharedData !== null) {
- //error_log("Citation not found in sharedData -- retrieving individually");
- }
- $html = Zotero_Cite::getCitationFromCiteServer($item, $queryParams);
- }
- $html = new SimpleXMLElement($html);
- $html['xmlns'] = Zotero_Atom::$nsXHTML;
- $subNode = dom_import_simplexml($html);
- $importedNode = $domDoc->importNode($subNode, true);
- $target->appendChild($importedNode);
- }
- else if ($type == 'bib') {
- if (!$multiFormat) {
- $target->setAttribute('type', 'xhtml');
- }
- if (isset($sharedData[$type][$item->libraryID . "/" . $item->key])) {
- $html = $sharedData[$type][$item->libraryID . "/" . $item->key];
- }
- else {
- if ($sharedData !== null) {
- //error_log("Bibliography not found in sharedData -- retrieving individually");
- }
- $html = Zotero_Cite::getBibliographyFromCitationServer(array($item), $queryParams);
- }
- $html = new SimpleXMLElement($html);
- $html['xmlns'] = Zotero_Atom::$nsXHTML;
- $subNode = dom_import_simplexml($html);
- $importedNode = $domDoc->importNode($subNode, true);
- $target->appendChild($importedNode);
- }
- else if ($type == 'json') {
- if ($queryParams['v'] < 2) {
- $target->setAttributeNS(
- Zotero_Atom::$nsZoteroAPI,
- "zapi:etag",
- $item->etag
- );
- }
- $textNode = $domDoc->createTextNode($item->toJSON(false, $queryParams, true));
- $target->appendChild($textNode);
- }
- else if ($type == 'csljson') {
- $arr = $item->toCSLItem();
- $json = Zotero_Utilities::formatJSON($arr);
- $textNode = $domDoc->createTextNode($json);
- $target->appendChild($textNode);
- }
- else if (in_array($type, Zotero_Translate::$exportFormats)) {
- $exportParams = $queryParams;
- $exportParams['format'] = $type;
- $export = Zotero_Translate::doExport([$item], $exportParams);
- $target->setAttribute('type', $export['mimeType']);
- // Insert XML into document
- if (preg_match('/\+xml$/', $export['mimeType'])) {
- // Strip prolog
- $body = preg_replace('/^<\?xml.+\n/', "", $export['body']);
- $subNode = $domDoc->createDocumentFragment();
- $subNode->appendXML($body);
- $target->appendChild($subNode);
- }
- else {
- $textNode = $domDoc->createTextNode($export['body']);
- $target->appendChild($textNode);
- }
- }
- }
-
- // TEMP
- if ($xmlstr) {
- $uncached = $xml->saveXML();
- if ($xmlstr != $uncached) {
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
- $uncached = str_replace(
- ' ',
- ' ',
- $uncached
- );
-
- if ($xmlstr != $uncached) {
- error_log("Cached Atom item entry does not match");
- error_log(" Cached: " . $xmlstr);
- error_log("Uncached: " . $uncached);
-
- Z_Core::$MC->set($cacheKey, $uncached, 3600); // 1 hour for now
- }
- }
- }
- else {
- $xmlstr = $xml->saveXML();
- 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 $xml;
- }
-
-
- /**
- * Import an item by URL using the translation server
- *
- * Initial request:
- *
- * {
- * "url": "http://..."
- * }
- *
- * Item selection for multi-item results:
- *
- * {
- * "url": "http://...",
- * "token": ""
- * "items": {
- * "0": "Item 1 Title",
- * "3": "Item 2 Title"
- * }
- * }
- *
- * Returns an array of keys of added items (like updateMultipleFromJSON) or an object
- * with a 'select' property containing an array of titles for multi-item results
- */
- public static function addFromURL($json, $requestParams, $libraryID, $userID,
- Zotero_Permissions $permissions, $translationToken) {
- if (!$translationToken) {
- throw new Exception("Translation token not provided");
- }
-
- self::validateJSONURL($json, $requestParams);
-
- $cacheKey = 'addFromURLKeyMappings_' . md5($json->url . $translationToken);
-
- // Replace numeric keys with URLs for selected items
- if (isset($json->items) && $requestParams['v'] >= 2) {
- $keyMappings = Z_Core::$MC->get($cacheKey);
- $newItems = [];
- foreach ($json->items as $number => $title) {
- if (!isset($keyMappings[$number])) {
- throw new Exception("Index '$number' not found for URL and token", Z_ERROR_INVALID_INPUT);
- }
- $url = $keyMappings[$number];
- $newItems[$url] = $title;
- }
- $json->items = $newItems;
- }
-
- $response = Zotero_Translate::doWeb(
- $json->url,
- $translationToken,
- isset($json->items) ? $json->items : null
- );
-
- if (!$response || is_int($response)) {
- return $response;
- }
-
- if (isset($response->items)) {
- $items = $response->items;
-
- // APIv3
- if ($requestParams['v'] >= 3) {
- for ($i = 0, $len = sizeOf($items); $i < $len; $i++) {
- // Assign key here so that we can add notes if necessary
- do {
- $itemKey = Zotero_ID::getKey();
- }
- while (Zotero_Items::existsByLibraryAndKey($libraryID, $itemKey));
- $items[$i]->key = $itemKey;
- // TEMP: translation-server shouldn't include these, but as long as it does,
- // remove them
- unset($items[$i]->itemKey);
- unset($items[$i]->itemVersion);
-
- // Pull out notes and stick in separate items
- if (isset($items[$i]->notes)) {
- foreach ($items[$i]->notes as $note) {
- $newNote = (object) [
- "itemType" => "note",
- "note" => $note->note,
- "parentItem" => $itemKey
- ];
- $items[] = $newNote;
- }
- unset($items[$i]->notes);
- }
-
- // TODO: link attachments, or not possible from translation-server?
- }
-
- $response = $items;
- }
- // APIv2 (was this ever used? it's possible the bookmarklet used v1 and we never publicized
- // this for v2)
- else if ($requestParams['v'] == 2) {
- for ($i = 0, $len = sizeOf($items); $i < $len; $i++) {
- // Assign key here so that we can add notes if necessary
- do {
- $itemKey = Zotero_ID::getKey();
- }
- while (Zotero_Items::existsByLibraryAndKey($libraryID, $itemKey));
- $items[$i]->itemKey = $itemKey;
-
- // Pull out notes and stick in separate items
- if (isset($items[$i]->notes)) {
- foreach ($items[$i]->notes as $note) {
- $newNote = (object) [
- "itemType" => "note",
- "note" => $note->note,
- "parentItem" => $itemKey
- ];
- $items[] = $newNote;
- }
- unset($items[$i]->notes);
- }
-
- // TODO: link attachments, or not possible from translation-server?
- }
- }
- // APIv1
- else {
- for ($i = 0, $len = sizeOf($items); $i < $len; $i++) {
- unset($items[$i]->key);
- unset($items[$i]->version);
- unset($items[$i]->itemKey);
- unset($items[$i]->itemVersion);
- }
- }
-
- try {
- self::validateMultiObjectJSON($response, $requestParams);
- }
- catch (Exception $e) {
- error_log($e);
- error_log(json_encode($response));
- throw new Exception("Invalid JSON from doWeb()");
- }
- }
- // Multi-item select
- else if (isset($response->select)) {
- // Replace URLs with numeric keys for found items
- if ($requestParams['v'] >= 2) {
- $keyMappings = [];
- $newItems = new stdClass;
- $number = 0;
- foreach ($response->select as $url => $title) {
- $keyMappings[$number] = $url;
- $newItems->$number = $title;
- $number++;
- }
- Z_Core::$MC->set($cacheKey, $keyMappings, 600);
- $response->select = $newItems;
- }
- return $response;
- }
- else {
- throw new Exception("Invalid return value from doWeb()");
- }
-
- return self::updateMultipleFromJSON(
- $response,
- $requestParams,
- $libraryID,
- $userID,
- $permissions,
- false,
- null
- );
- }
-
-
- public static function updateFromJSON(Zotero_Item $item,
- $json,
- Zotero_Item $parentItem=null,
- $requestParams,
- $userID,
- $requireVersion=0,
- $partialUpdate=false) {
- $json = Zotero_API::extractEditableJSON($json);
- $exists = Zotero_API::processJSONObjectKey($item, $json, $requestParams);
- $apiVersion = $requestParams['v'];
-
- // computerProgram used 'version' instead of 'versionNumber' before v3
- if ($apiVersion < 3 && isset($json->version)) {
- $json->versionNumber = $json->version;
- unset($json->version);
- }
-
- Zotero_API::checkJSONObjectVersion($item, $json, $requestParams, $requireVersion);
- self::validateJSONItem(
- $json,
- $item->libraryID,
- $exists ? $item : null,
- $parentItem || ($exists ? !!$item->getSourceKey() : false),
- $requestParams,
- $partialUpdate && $exists
- );
-
- $changed = false;
- $twoStage = false;
-
- if (!Zotero_DB::transactionInProgress()) {
- Zotero_DB::beginTransaction();
- $transactionStarted = true;
- }
- else {
- $transactionStarted = false;
- }
-
- // Set itemType first
- if (isset($json->itemType)) {
- $item->setField("itemTypeID", Zotero_ItemTypes::getID($json->itemType));
- }
-
- $dateModifiedProvided = false;
- // APIv2 and below
- $changedDateModified = false;
- // Limit new Date Modified handling to Zotero for now. It can be applied to all v3 clients
- // once people have time to update their code.
- $tmpZoteroClientDateModifiedHack = !empty($_SERVER['HTTP_USER_AGENT'])
- && (strpos($_SERVER['HTTP_USER_AGENT'], 'Firefox') !== false
- || strpos($_SERVER['HTTP_USER_AGENT'], 'Zotero') !== false);
-
- foreach ($json as $key=>$val) {
- switch ($key) {
- case 'key':
- case 'version':
- case 'itemKey':
- case 'itemVersion':
- case 'itemType':
- case 'deleted':
- case 'inPublications':
- continue;
-
- case 'parentItem':
- $item->setSourceKey($val);
- break;
-
- case 'creators':
- if (!$val && !$item->numCreators()) {
- continue 2;
- }
-
- $orderIndex = -1;
- foreach ($val as $newCreatorData) {
- // JSON uses 'name' and 'firstName'/'lastName',
- // so switch to just 'firstName'/'lastName'
- if (isset($newCreatorData->name)) {
- $newCreatorData->firstName = '';
- $newCreatorData->lastName = $newCreatorData->name;
- unset($newCreatorData->name);
- $newCreatorData->fieldMode = 1;
- }
- else {
- $newCreatorData->fieldMode = 0;
- }
-
- // Skip empty creators
- if (Zotero_Utilities::unicodeTrim($newCreatorData->firstName) === ""
- && Zotero_Utilities::unicodeTrim($newCreatorData->lastName) === "") {
- break;
- }
-
- $orderIndex++;
-
- $newCreatorTypeID = Zotero_CreatorTypes::getID($newCreatorData->creatorType);
-
- // Same creator in this position
- $existingCreator = $item->getCreator($orderIndex);
- if ($existingCreator && $existingCreator['ref']->equals($newCreatorData)) {
- // Just change the creatorTypeID
- if ($existingCreator['creatorTypeID'] != $newCreatorTypeID) {
- $item->setCreator($orderIndex, $existingCreator['ref'], $newCreatorTypeID);
- }
- continue;
- }
-
- // Same creator in a different position, so use that
- $existingCreators = $item->getCreators();
- for ($i=0,$len=sizeOf($existingCreators); $i<$len; $i++) {
- if ($existingCreators[$i]['ref']->equals($newCreatorData)) {
- $item->setCreator($orderIndex, $existingCreators[$i]['ref'], $newCreatorTypeID);
- continue;
- }
- }
-
- // Make a fake creator to use for the data lookup
- $newCreator = new Zotero_Creator;
- $newCreator->libraryID = $item->libraryID;
- foreach ($newCreatorData as $key=>$val) {
- if ($key == 'creatorType') {
- continue;
- }
- $newCreator->$key = $val;
- }
-
- // Look for an equivalent creator in this library
- $candidates = Zotero_Creators::getCreatorsWithData($item->libraryID, $newCreator, true);
- if ($candidates) {
- $c = Zotero_Creators::get($item->libraryID, $candidates[0]);
- $item->setCreator($orderIndex, $c, $newCreatorTypeID);
- continue;
- }
-
- // None found, so make a new one
- $creatorID = $newCreator->save();
- $newCreator = Zotero_Creators::get($item->libraryID, $creatorID);
- $item->setCreator($orderIndex, $newCreator, $newCreatorTypeID);
- }
-
- // Remove all existing creators above the current index
- if ($exists && $indexes = array_keys($item->getCreators())) {
- $i = max($indexes);
- while ($i>$orderIndex) {
- $item->removeCreator($i);
- $i--;
- }
- }
-
- break;
-
- case 'tags':
- $item->setTags($val);
- break;
-
- case 'collections':
- $item->setCollections($val);
- break;
-
- case 'relations':
- $item->setRelations($val);
- break;
-
- case 'attachments':
- case 'notes':
- if (!$val) {
- continue;
- }
- $twoStage = true;
- break;
-
- case 'note':
- $item->setNote($val);
- break;
-
- // Attachment properties
- case 'linkMode':
- $item->attachmentLinkMode = Zotero_Attachments::linkModeNameToNumber($val, true);
- break;
-
- case 'contentType':
- case 'charset':
- case 'filename':
- case 'path':
- $k = "attachment" . ucwords($key);
- // Until classic sync is removed, store paths in Mozilla relative descriptor style,
- // and then batch convert and remove this
- if ($key == 'path') {
- $val = Zotero_Attachments::encodeRelativeDescriptorString($val);
- }
- $item->$k = $val;
- break;
-
- case 'md5':
- $item->attachmentStorageHash = $val;
- break;
-
- case 'mtime':
- $item->attachmentStorageModTime = $val;
- break;
-
- case 'dateModified':
- if ($apiVersion >= 3 && $tmpZoteroClientDateModifiedHack) {
- $item->setField($key, $val);
- $dateModifiedProvided = true;
- }
- else {
- $changedDateModified = $item->setField($key, $val);
- }
- break;
-
- default:
- $item->setField($key, $val);
- break;
- }
- }
-
- if ($parentItem) {
- $item->setSource($parentItem->id);
- }
- // Clear parent if not a partial update and a parentItem isn't provided
- else if ($apiVersion >= 2 && !$partialUpdate
- && $item->getSourceKey() && !isset($json->parentItem)) {
- $item->setSourceKey(false);
- }
-
- $item->deleted = !empty($json->deleted);
-
- if (isset($json->inPublications) || !$partialUpdate) {
- $item->inPublications = !empty($json->inPublications);
- }
-
- // Skip "Date Modified" update if only certain fields were updated (e.g., collections)
- $skipDateModifiedUpdate = $dateModifiedProvided || !sizeOf(array_diff(
- $item->getChanged(),
- ['collections', 'deleted', 'inPublications', 'relations', 'tags']
- ));
-
- if ($item->hasChanged() && !$skipDateModifiedUpdate
- && (($apiVersion >= 3 && $tmpZoteroClientDateModifiedHack) || !$changedDateModified)) {
- // Update item with the current timestamp
- $item->dateModified = Zotero_DB::getTransactionTimestamp();
- }
-
- $changed = $item->save($userID) || $changed;
-
- // Additional steps that have to be performed on a saved object
- if ($twoStage) {
- foreach ($json as $key=>$val) {
- switch ($key) {
- case 'attachments':
- if (!$val) {
- continue;
- }
- foreach ($val as $attachmentJSON) {
- $childItem = new Zotero_Item;
- $childItem->libraryID = $item->libraryID;
- self::updateFromJSON(
- $childItem,
- $attachmentJSON,
- $item,
- $requestParams,
- $userID
- );
- }
- break;
-
- case 'notes':
- if (!$val) {
- continue;
- }
- $noteItemTypeID = Zotero_ItemTypes::getID("note");
-
- foreach ($val as $note) {
- $childItem = new Zotero_Item;
- $childItem->libraryID = $item->libraryID;
- $childItem->itemTypeID = $noteItemTypeID;
- $childItem->setSource($item->id);
- $childItem->setNote($note->note);
- $childItem->save();
- }
- break;
- }
- }
- }
-
- if ($transactionStarted) {
- Zotero_DB::commit();
- }
-
- return $changed;
- }
-
-
- 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'];
- $libraryType = Zotero_Libraries::getType($libraryID);
-
- // Check if child item is being converted to top-level or vice-versa, and update $isChild to the
- // target state so that, e.g., we properly check for the required property 'collections' below
- // when converting a child item to a top-level item
- if ($isChild) {
- // PATCH
- if (($partialUpdate && isset($json->parentItem) && $json->parentItem === false)
- // PUT
- || (!$partialUpdate && (!isset($json->parentItem) || $json->parentItem === false))) {
- $isChild = false;
- }
- }
- else {
- if (isset($json->parentItem) && $json->parentItem !== false) {
- $isChild = true;
- }
- }
-
- 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 or false", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- case 'itemType':
- if (!is_string($val)) {
- throw new Exception("'itemType' must be a string", Z_ERROR_INVALID_INPUT);
- }
-
- // TODO: Don't allow changing item type
-
- if (!Zotero_ItemTypes::getID($val)) {
- throw new Exception("'$val' is not a valid itemType", Z_ERROR_INVALID_INPUT);
- }
-
- // Parent/child checks by item type
- 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);
- }
- }
- 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_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);
- }
- 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)
- // Allow an empty array, because it's annoying for some clients otherwise
- && !(is_array($val) && empty($val))) {
- throw new Exception("'$key' property must be an object", Z_ERROR_INVALID_INPUT);
- }
- foreach ($val as $predicate => $object) {
- if (!in_array($predicate, Zotero_Relations::$allowedItemPredicates)) {
- throw new Exception("Unsupported predicate '$predicate'", Z_ERROR_INVALID_INPUT);
- }
-
- // Certain predicates allow values other than Zotero URIs
- if (in_array($predicate, Zotero_Relations::$externalPredicates)) {
- continue;
- }
-
- $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;
-
- case 'inPublications':
- if (!$val) {
- break;
- }
-
- if ($libraryType != 'user') {
- throw new Exception(
- ucwords($libraryType) . " items cannot be added to My Publications",
- Z_ERROR_INVALID_INPUT
- );
- }
-
- if (!$isChild && ($itemType == 'note' || $itemType == 'attachment')) {
- throw new Exception(
- "Top-level notes and attachments cannot be added to My Publications",
- Z_ERROR_INVALID_INPUT
- );
- }
-
- if ($itemType == 'attachment') {
- $linkMode = isset($json->linkMode)
- ? strtolower($json->linkMode)
- : $item->attachmentLinkMode;
- if ($linkMode == 'linked_file') {
- throw new Exception(
- "Linked-file attachments cannot be added to My Publications",
- Z_ERROR_INVALID_INPUT
- );
- }
- }
- break;
-
- // Attachment properties
- case 'linkMode':
- try {
- $linkMode = Zotero_Attachments::linkModeNumberToName(
- 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':
- case 'path':
- if ($itemType != 'attachment') {
- throw new Exception("'$key' is valid only for attachment items", Z_ERROR_INVALID_INPUT);
- }
-
- $linkMode = isset($json->linkMode)
- ? strtolower($json->linkMode)
- : $item->attachmentLinkMode;
-
- switch ($key) {
- case 'filename':
- case 'md5':
- case 'mtime':
- if (strpos($linkMode, 'imported_') !== 0) {
- throw new Exception("'$key' is valid only for imported attachment items", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- case 'path':
- if ($linkMode != 'linked_file') {
- throw new Exception("'$key' is valid only for linked file attachment items", Z_ERROR_INVALID_INPUT);
- }
- break;
- }
-
- switch ($key) {
- case 'contentType':
- case 'charset':
- case 'filename':
- case 'path':
- $propName = 'attachment' . ucwords($key);
- break;
-
- case 'md5':
- $propName = 'attachmentStorageHash';
- break;
-
- case 'mtime':
- $propName = 'attachmentStorageModTime';
- break;
- }
-
- if (($key == 'mtime' || $key == 'md5') && $libraryType == '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);
- }
- // Don't allow dateAdded to change
- if ($val != $item->$key) {
- // If passed dateAdded is exactly one hour or one day off, assume it's from
- // a DST bug we haven't yet tracked down
- // (https://github.com/zotero/zotero/issues/1201) and ignore it
- $absTimeDiff = abs(strtotime($val) - strtotime($item->$key));
- if ($absTimeDiff == 3600 || $absTimeDiff == 86400
- // Allow for Quick Start Guide items from <=4.0
- || $item->key == 'ABCD2345' || $item->key == 'ABCD3456') {
- $json->$key = $item->$key;
- }
- else {
- 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;
- }
- }
- }
-
-
- private static function validateJSONURL($json) {
- if (!is_object($json)) {
- throw new Exception("Unexpected " . gettype($json) . " '" . $json . "'", Z_ERROR_INVALID_INPUT);
- }
-
- if (!isset($json->url)) {
- throw new Exception("URL not provided");
- }
-
- if (!is_string($json->url)) {
- throw new Exception("'url' must be a string", Z_ERROR_INVALID_INPUT);
- }
-
- if (isset($json->items) && !is_object($json->items)) {
- throw new Exception("'items' must be an object", Z_ERROR_INVALID_INPUT);
- }
-
- if (isset($json->token) && !is_string($json->token)) {
- throw new Exception("Invalid token", Z_ERROR_INVALID_INPUT);
- }
-
- foreach ($json as $key => $val) {
- if (!in_array($key, array('url', 'token', 'items'))) {
- throw new Exception("Invalid property '$key'", Z_ERROR_INVALID_INPUT);
- }
-
- if ($key == 'items' && sizeOf($val) > Zotero_API::$maxTranslateItems) {
- throw new Exception("Cannot translate more than " . Zotero_API::$maxTranslateItems . " items at a time", Z_ERROR_UPLOAD_TOO_LARGE);
- }
- }
- }
-
-
- private static function loadItems($libraryID, $itemIDs=array()) {
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- $sql = self::getPrimaryDataSQL() . "1";
-
- // TODO: optimize
- if ($itemIDs) {
- foreach ($itemIDs as $itemID) {
- if (!is_int($itemID)) {
- throw new Exception("Invalid itemID $itemID");
- }
- }
- $sql .= ' AND itemID IN ('
- . implode(',', array_fill(0, sizeOf($itemIDs), '?'))
- . ')';
- }
-
- $stmt = Zotero_DB::getStatement($sql, "loadItems_" . sizeOf($itemIDs), $shardID);
- $itemRows = Zotero_DB::queryFromStatement($stmt, $itemIDs);
- $loadedItemIDs = array();
-
- if ($itemRows) {
- foreach ($itemRows as $row) {
- if ($row['libraryID'] != $libraryID) {
- throw new Exception("Item $itemID isn't in library $libraryID", Z_ERROR_OBJECT_LIBRARY_MISMATCH);
- }
-
- $itemID = $row['id'];
- $loadedItemIDs[] = $itemID;
-
- // Item isn't loaded -- create new object and stuff in array
- if (!isset(self::$objectCache[$itemID])) {
- $item = new Zotero_Item;
- $item->loadFromRow($row, true);
- self::$objectCache[$itemID] = $item;
- }
- // Existing item -- reload in place
- else {
- self::$objectCache[$itemID]->loadFromRow($row, true);
- }
- }
- }
-
- if (!$itemIDs) {
- // If loading all items, remove old items that no longer exist
- $ids = array_keys(self::$objectCache);
- foreach ($ids as $id) {
- if (!in_array($id, $loadedItemIDs)) {
- throw new Exception("Unimplemented");
- //$this->unload($id);
- }
- }
- }
- }
-
-
- public static function getSortTitle($title) {
- if (!$title) {
- return '';
- }
- return mb_strcut(preg_replace('/^[[({\-"\'“‘ ]+(.*)[\])}\-"\'”’ ]*?$/Uu', '$1', $title), 0, Zotero_Notes::$MAX_TITLE_LENGTH);
- }
-}
-
-Zotero_Items::init();
diff --git a/model/Key.inc.php b/model/Key.inc.php
deleted file mode 100644
index 0e37f210..00000000
--- a/model/Key.inc.php
+++ /dev/null
@@ -1,600 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Key {
- private $id;
- private $key;
- private $userID;
- private $name;
- private $dateAdded;
- private $lastUsed;
- private $permissions = array();
-
- private $loaded = false;
- private $changed = array();
- private $erased = false;
-
-
- public function __get($field) {
- if ($this->erased) {
- throw new Exception("Cannot access field '$field' of deleted key $this->id");
- }
-
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- switch ($field) {
- case 'id':
- case 'key':
- case 'userID':
- case 'name':
- case 'dateAdded':
- break;
-
- default:
- throw new Exception("Invalid key field '$field'");
- }
-
- return $this->$field;
- }
-
-
- public function __set($field, $value) {
- switch ($field) {
- // Set id and libraryID without loading
- case 'id':
- case 'key':
- if ($this->loaded) {
- throw new Exception("Cannot set $field after key is already loaded");
- }
- $this->$field = $value;
- return;
-
- case 'userID':
- case 'name':
- break;
-
- default:
- throw new Exception("Invalid key field '$field'");
- }
-
- if ($this->id || $this->key) {
- if (!$this->loaded) {
- $this->load();
- }
- }
- else {
- $this->loaded = true;
- }
-
- if ($this->$field == $value) {
- Z_Core::debug("Key $this->id $field value ($value) has not changed", 4);
- return;
- }
- $this->$field = $value;
- $this->changed[$field] = true;
- }
-
-
- public function getPermissions() {
- if ($this->erased) {
- throw new Exception("Cannot access permissions of deleted key $this->id");
- }
-
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- $permissions = new Zotero_Permissions($this->userID);
- foreach ($this->permissions as $libraryID=>$p) {
- foreach ($p as $key=>$val) {
- $permissions->setPermission($libraryID, $key, $val);
- }
- }
- return $permissions;
- }
-
-
- /*public function getPermission($libraryID, $permission) {
- if ($this->erased) {
- throw new Exception("Cannot access permission of deleted key $this->id");
- }
-
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- return $this->permissions[$libraryID][$permission];
- }*/
-
-
- /**
- * Examples:
- *
- * $keyObj->setPermission(12345, 'library', true);
- * $keyObj->setPermission(12345, 'notes', true);
- * $keyObj->setPermission(12345, 'files', true);
- * $keyObj->setPermission(12345, 'write', true);
- * $keyObj->setPermission(0, 'group', true);
- * $keyObj->setPermission(0, 'write', true);
- */
- public function setPermission($libraryID, $permission, $enabled) {
- if ($this->id || $this->key) {
- if (!$this->loaded) {
- $this->load();
- }
- }
- else {
- $this->loaded = true;
- }
-
- $enabled = !!$enabled;
-
- // libraryID=0 is a special case for all-group access
- if ($libraryID === 0) {
- // Convert 'group' to 'library'
- if ($permission == 'group') {
- $permission = 'library';
- }
- else if ($permission == 'write') {}
- else {
- throw new Exception("libraryID 0 is valid only with permission 'group'");
- }
- }
- else if ($permission == 'group') {
- throw new Exception("'group' permission is valid only with libraryID 0");
- }
- else if (!$libraryID) {
- throw new Exception("libraryID not set");
- }
-
- switch ($permission) {
- case 'library':
- case 'notes':
- case 'files':
- case 'write':
- break;
-
- default:
- throw new Exception("Invalid key permissions field '$permission'");
- }
-
- $this->permissions[$libraryID][$permission] = $enabled;
- $this->changed['permissions'][$libraryID][$permission] = true;
- }
-
-
-
- public function save() {
- if (!$this->loaded) {
- Z_Core::debug("Not saving unloaded key $this->id");
- return;
- }
-
- if (!$this->userID) {
- throw new Exception("Cannot save key without userID");
- }
-
- if (!$this->name) {
- throw new Exception("Cannot save key without name");
- }
-
- if (strlen($this->name) > 255) {
- throw new Exception("Key name too long", Z_ERROR_KEY_NAME_TOO_LONG);
- }
-
- Zotero_DB::beginTransaction();
-
- if (!$this->key) {
- $isNew = true;
- $this->key = Zotero_Keys::generate();
- }
- else {
- $isNew = false;
- }
-
- $fields = array(
- 'key',
- 'userID',
- 'name'
- );
-
- $sql = "INSERT INTO `keys` (keyID, `key`, userID, name) VALUES (?, ?, ?, ?)";
- $params = array($this->id);
- foreach ($fields as $field) {
- $params[] = $this->$field;
- }
- $sql .= " ON DUPLICATE KEY UPDATE ";
- $q = array();
- foreach ($fields as $field) {
- $q[] = "`$field`=?";
- $params[] = $this->$field;
- }
- $sql .= implode(", ", $q);
- $insertID = Zotero_DB::query($sql, $params);
-
- if (!$this->id) {
- if (!$insertID) {
- throw new Exception("Key id not available after INSERT");
- }
- $this->id = $insertID;
- }
-
- if (!$insertID) {
- $sql = "SELECT * FROM keyPermissions WHERE keyID=?";
- $oldRows = Zotero_DB::query($sql, $this->id);
- }
- $oldPermissions = [];
- $newPermissions = [];
- $librariesToAdd = [];
- $librariesToRemove = [];
-
- // Massage rows into permissions format
- if (!$isNew && isset($oldRows)) {
- foreach ($oldRows as $row) {
- $oldPermissions[$row['libraryID']][$row['permission']] = !!$row['granted'];
- }
- }
-
- // Delete existing permissions
- $sql = "DELETE FROM keyPermissions WHERE keyID=?";
- Zotero_DB::query($sql, $this->id);
-
- if (isset($this->changed['permissions'])) {
- foreach ($this->changed['permissions'] as $libraryID=>$p) {
- foreach ($p as $permission=>$changed) {
- $enabled = $this->permissions[$libraryID][$permission];
- if (!$enabled) {
- continue;
- }
-
- $sql = "INSERT INTO keyPermissions VALUES (?, ?, ?, ?)";
- // TODO: support negative permissions
- Zotero_DB::query($sql, array($this->id, $libraryID, $permission, 1));
-
- $newPermissions[$libraryID][$permission] = true;
- }
- }
- }
-
- $this->permissions = $newPermissions;
-
- // Send notifications for added and removed API key – library pairs
- if (!$isNew) {
- $librariesToAdd = $this->permissionsDiff(
- $oldPermissions, $newPermissions, $this->userID
- );
- $librariesToRemove = $this->permissionsDiff(
- $newPermissions, $oldPermissions, $this->userID
- );
- if ($librariesToAdd) {
- Zotero_Notifier::trigger(
- 'add',
- 'apikey-library',
- array_map(function ($libraryID) {
- return $this->id . "-" . $libraryID;
- }, array_unique($librariesToAdd))
- );
- }
- if ($librariesToRemove) {
- Zotero_Notifier::trigger(
- 'remove',
- 'apikey-library',
- array_map(function ($libraryID) {
- return $this->id . "-" . $libraryID;
- }, array_unique($librariesToRemove))
- );
- }
- }
-
- Zotero_DB::commit();
-
- $this->load();
-
- return $this->id;
- }
-
-
- /**
- * Calculate the difference between two sets of permissions,
- * taking all-group access into account
- */
- private function permissionsDiff($permissions1, $permissions2, $userID) {
- $diff = [];
- $userGroupLibraries = Zotero_Groups::getUserGroupLibraries($userID);
- foreach ($permissions2 as $libraryID => $libraryPermissions) {
- if (!$libraryPermissions['library']) {
- continue;
- }
- if (empty($permissions1[$libraryID]['library'])) {
- // If second set has a 0 (all-group access), diff is user's groups not
- // explicitly listed in first set
- if ($libraryID === 0) {
- $diff = array_merge(
- $diff,
- array_filter(
- $userGroupLibraries,
- function ($libraryID) use ($permissions1) {
- return empty($permissions1[$libraryID]['library']);
- }
- )
- );
- }
- else {
- $libraryType = Zotero_Libraries::getType($libraryID);
- if ($libraryType == 'user'
- || ($libraryType == 'group' && empty($permissions1[0]['library']))) {
- $diff[] = $libraryID;
- }
- }
- }
- }
- return $diff;
- }
-
-
- public function erase() {
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- Zotero_DB::beginTransaction();
-
- $sql = "DELETE FROM `keys` WHERE keyID=?";
- $deleted = Zotero_DB::query($sql, $this->id);
- if (!$deleted) {
- throw new Exception("Key not deleted");
- }
-
- Zotero_DB::commit();
-
- $this->erased = true;
- }
-
-
- /**
- * Converts key to a SimpleXMLElement item
- *
- * @return SimpleXMLElement Key data as SimpleXML element
- */
- public function toXML() {
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- $xml = ' ';
- $xml = new SimpleXMLElement($xml);
-
- $xml['key'] = $this->key;
- $xml['dateAdded'] = $this->dateAdded;
- if ($this->lastUsed != '0000-00-00 00:00:00') {
- $xml['lastUsed'] = $this->lastUsed;
- }
- $xml->name = $this->name;
-
- if ($this->permissions) {
- foreach ($this->permissions as $libraryID=>$p) {
- $access = $xml->addChild('access');
-
- // group="all" is stored as libraryID 0
- if ($libraryID === 0) {
- $access['group'] = 'all';
- if (!empty($p['write'])) {
- $access['write'] = 1;
- }
- continue;
- }
-
- $type = Zotero_Libraries::getType($libraryID);
- switch ($type) {
- case 'user':
- foreach ($p as $permission=>$granted) {
- $access[$permission] = (int) $granted;
- }
- break;
-
- case 'group':
- $access['group'] = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
- if (!empty($p['write'])) {
- $access['write'] = 1;
- }
- break;
- }
- }
- }
-
- $ips = $this->getRecentIPs();
- if ($ips) {
- $xml->recentIPs = implode(' ', $ips);
- }
-
- return $xml;
- }
-
-
- public function toJSON() {
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load();
- }
-
- $json = [];
- if (!empty($_GET['showid'])) {
- $json['id'] = $this->id;
- }
- $json['key'] = $this->key;
- $json['userID'] = $this->userID;
- $json['username'] = Zotero_Users::getUsername($this->userID);
- $json['name'] = $this->name;
-
- if ($this->permissions) {
- $json['access'] = [
- 'user' => [],
- 'groups' => []
- ];
-
- foreach ($this->permissions as $libraryID=>$p) {
- // group="all" is stored as libraryID 0
- if ($libraryID === 0) {
- $json['access']['groups']['all']['library'] = true;
- $json['access']['groups']['all']['write'] = !empty($p['write']);
- }
- else {
- $type = Zotero_Libraries::getType($libraryID);
- switch ($type) {
- case 'user':
- $json['access']['user']['library'] = true;
- foreach ($p as $permission=>$granted) {
- if ($permission == 'library') {
- continue;
- }
- $json['access']['user'][$permission] = (bool) $granted;
- }
- break;
-
- case 'group':
- $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
- $json['access']['groups'][$groupID]['library'] = true;
- $json['access']['groups'][$groupID]['write'] = !empty($p['write']);
- break;
- }
- }
- }
- if (sizeOf($json['access']['user']) === 0) {
- unset($json['access']['user']);
- }
- if (sizeOf($json['access']['groups']) === 0) {
- unset($json['access']['groups']);
- }
- }
-
- $json['dateAdded'] = Zotero_Date::sqlToISO8601($this->dateAdded);
- if ($this->lastUsed != '0000-00-00 00:00:00') {
- $json['lastUsed'] = Zotero_Date::sqlToISO8601($this->lastUsed);
- }
-
- $ips = $this->getRecentIPs();
- if ($ips) {
- $json['recentIPs'] = $ips;
- }
-
- return $json;
- }
-
-
- public function loadFromRow($row) {
- foreach ($row as $field=>$val) {
- switch ($field) {
- case 'keyID':
- $this->id = $val;
- break;
-
- default:
- $this->$field = $val;
- }
- }
-
- $this->loaded = true;
- $this->changed = array();
- $this->permissions = array();
- }
-
-
- public function logAccess() {
- if (!$this->id) {
- throw new Exception("Key not loaded");
- }
-
- $ip = IPAddress::getIP();
-
- // If we already logged access by this key from this IP address
- // in the last minute, don't do it again
- $cacheKey = "keyAccessLogged_" . $this->id . "_" . md5($ip);
- if (Z_Core::$MC->get($cacheKey)) {
- return;
- }
-
- try {
- $sql = "UPDATE `keys` SET lastUsed=NOW() WHERE keyID=?";
- Zotero_DB::query($sql, $this->id, 0, [ 'writeInReadMode' => true ]);
- if ($ip) {
- $sql = "REPLACE INTO keyAccessLog (keyID, ipAddress) VALUES (?, INET_ATON(?))";
- Zotero_DB::query($sql, [$this->id, $ip], 0, [ 'writeInReadMode' => true ]);
- }
- }
- catch (Exception $e) {
- error_log("WARNING: " . $e);
- }
-
- Z_Core::$MC->set($cacheKey, "1", 600);
- }
-
-
- private function load() {
- if ($this->id) {
- $sql = "SELECT * FROM `keys` WHERE keyID=?";
- $row = Zotero_DB::rowQuery($sql, $this->id);
- }
- else if ($this->key) {
- $sql = "SELECT * FROM `keys` WHERE `key`=?";
- $row = Zotero_DB::rowQuery($sql, $this->key);
- }
- if (!$row) {
- return false;
- }
-
- $this->loadFromRow($row);
-
- $sql = "SELECT * FROM keyPermissions WHERE keyID=?";
- $rows = Zotero_DB::query($sql, $this->id);
- foreach ($rows as $row) {
- $this->permissions[$row['libraryID']][$row['permission']] = !!$row['granted'];
-
- if ($row['permission'] == 'library') {
- // Key-based access to library provides file access as well
- $this->permissions[$row['libraryID']]['files'] = !!$row['granted'];
-
- if ($row['libraryID'] === 0 || Zotero_Libraries::getType($row['libraryID']) == 'group') {
- // Key-based access to group libraries implies view and note access
- $this->permissions[$row['libraryID']]['view'] = !!$row['granted'];
- $this->permissions[$row['libraryID']]['notes'] = !!$row['granted'];
- }
- }
- }
- }
-
-
- private function getRecentIPs() {
- $sql = "SELECT INET_NTOA(ipAddress) FROM keyAccessLog WHERE keyID=?
- ORDER BY timestamp DESC LIMIT 5";
- $ips = Zotero_DB::columnQuery($sql, $this->id);
- if (!$ips) {
- return array();
- }
- return $ips;
- }
-}
-?>
diff --git a/model/Keys.inc.php b/model/Keys.inc.php
deleted file mode 100644
index 7aea6acb..00000000
--- a/model/Keys.inc.php
+++ /dev/null
@@ -1,102 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Keys {
- public static function getByKey($key) {
- $sql = "SELECT keyID FROM `keys` WHERE `key`=?";
- $keyID = Zotero_DB::valueQuery($sql, $key);
- if (!$keyID) {
- return false;
- }
- $keyObj = new Zotero_Key;
- $keyObj->id = $keyID;
- return $keyObj;
- }
-
-
- public static function getUserKeys($userID) {
- $keys = array();
- $keyIDs = Zotero_DB::columnQuery("SELECT keyID FROM `keys` WHERE userID=?", $userID);
- if ($keyIDs) {
- foreach ($keyIDs as $keyID) {
- $keyObj = new Zotero_Key;
- $keyObj->id = $keyID;
- $keys[] = $keyObj;
- }
- }
- return $keys;
- }
-
-
- 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;
- }
-
-
- public static function authenticate($key) {
- $keyObj = self::getByKey($key);
- if (!$keyObj) {
- // TODO: log auth failure
- return false;
- }
- $keyObj->logAccess();
- return $keyObj;
- }
-
-
- public static function generate() {
- $tries = 5;
- while ($tries > 0) {
- $str = Zotero_Utilities::randomString(24, 'mixed');
- $sql = "SELECT COUNT(*) FROM `keys` WHERE `key`=?";
- if (Zotero_DB::valueQuery($sql, $str)) {
- $tries--;
- continue;
- }
- return $str;
- }
- throw new Exception("Unique key could not be generated");
- }
-}
-?>
diff --git a/model/Libraries.inc.php b/model/Libraries.inc.php
deleted file mode 100644
index b531fe87..00000000
--- a/model/Libraries.inc.php
+++ /dev/null
@@ -1,525 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Libraries {
- private static $libraryTypeCache = array();
- private static $originalVersions = array();
- private static $updatedVersions = array();
-
- public static function add($type, $shardID) {
- if (!$shardID) {
- throw new Exception('$shardID not provided');
- }
-
- Zotero_DB::beginTransaction();
-
- $sql = "INSERT INTO libraries (libraryType, shardID) VALUES (?,?)";
- $libraryID = Zotero_DB::query($sql, array($type, $shardID));
-
- $sql = "INSERT INTO shardLibraries (libraryID, libraryType) VALUES (?,?)";
- Zotero_DB::query($sql, array($libraryID, $type), $shardID);
-
- Zotero_DB::commit();
-
- return $libraryID;
- }
-
-
- public static function exists($libraryID) {
- $sql = "SELECT COUNT(*) FROM libraries WHERE libraryID=?";
- return !!Zotero_DB::valueQuery($sql, $libraryID);
- }
-
-
- public static function getName($libraryID) {
- $type = self::getType($libraryID);
- switch ($type) {
- case 'user':
- $userID = Zotero_Users::getUserIDFromLibraryID($libraryID);
- return Zotero_Users::getUsername($userID);
-
- case 'publications':
- $userID = Zotero_Users::getUserIDFromLibraryID($libraryID);
- return Zotero_Users::getUsername($userID) . "’s Publications";
-
- case 'group':
- $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
- $group = Zotero_Groups::get($groupID);
- return $group->name;
-
- default:
- throw new Exception("Invalid library type '$libraryType'");
- }
- }
-
-
- /**
- * Get the type-specific id (userID or groupID) of the library
- */
- public static function getLibraryTypeID($libraryID) {
- $type = self::getType($libraryID);
- switch ($type) {
- case 'user':
- return Zotero_Users::getUserIDFromLibraryID($libraryID);
-
- case 'publications':
- throw new Exception("Cannot get library type id of publications library");
-
- case 'group':
- return Zotero_Groups::getGroupIDFromLibraryID($libraryID);
-
- default:
- throw new Exception("Invalid library type '$libraryType'");
- }
- }
-
-
- public static function getType($libraryID) {
- if (!$libraryID) {
- throw new Exception("Library not provided");
- }
-
- if (isset(self::$libraryTypeCache[$libraryID])) {
- return self::$libraryTypeCache[$libraryID];
- }
-
- $cacheKey = 'libraryType_' . $libraryID;
- $libraryType = Z_Core::$MC->get($cacheKey);
- if ($libraryType) {
- self::$libraryTypeCache[$libraryID] = $libraryType;
- return $libraryType;
- }
- $sql = "SELECT libraryType FROM libraries WHERE libraryID=?";
- $libraryType = Zotero_DB::valueQuery($sql, $libraryID);
- if (!$libraryType) {
- trigger_error("Library $libraryID does not exist", E_USER_ERROR);
- }
-
- self::$libraryTypeCache[$libraryID] = $libraryType;
- Z_Core::$MC->set($cacheKey, $libraryType);
-
- return $libraryType;
- }
-
-
- public static function getOwner($libraryID) {
- return Zotero_Users::getUserIDFromLibraryID($libraryID);
- }
-
-
- public static function getUserLibraries($userID) {
- return array_merge(
- array(Zotero_Users::getLibraryIDFromUserID($userID)),
- Zotero_Groups::getUserGroupLibraries($userID)
- );
- }
-
-
- public static function updateVersionAndTimestamp($libraryID) {
- $timestamp = self::updateTimestamps($libraryID);
- Zotero_DB::registerTransactionTimestamp($timestamp);
- self::updateVersion($libraryID);
- }
-
-
- public static function getTimestamp($libraryID) {
- $sql = "SELECT lastUpdated FROM shardLibraries WHERE libraryID=?";
- return Zotero_DB::valueQuery(
- $sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID)
- );
- }
-
-
- public static function getUserLibraryUpdateTimes($userID) {
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
- $sql = "SELECT libraryID, UNIX_TIMESTAMP(lastUpdated) AS lastUpdated FROM libraries
- WHERE libraryID IN ("
- . implode(',', array_fill(0, sizeOf($libraryIDs), '?'))
- . ") LOCK IN SHARE MODE";
- $rows = Zotero_DB::query($sql, $libraryIDs);
- $updateTimes = array();
- foreach ($rows as $row) {
- $updateTimes[$row['libraryID']] = $row['lastUpdated'];
- }
- return $updateTimes;
- }
-
-
- public static function updateTimestamps($libraryIDs) {
- if (is_scalar($libraryIDs)) {
- if (!is_numeric($libraryIDs)) {
- throw new Exception("Invalid library ID");
- }
- $libraryIDs = array($libraryIDs);
- }
-
- Zotero_DB::beginTransaction();
-
- // TODO: replace with just shardLibraries after sync code removal
- $sql = "UPDATE libraries SET lastUpdated=NOW() WHERE libraryID IN "
- . "(" . implode(',', array_fill(0, sizeOf($libraryIDs), '?')) . ")";
- Zotero_DB::query($sql, $libraryIDs);
-
- $sql = "SELECT UNIX_TIMESTAMP(lastUpdated) FROM libraries WHERE libraryID=?";
- $timestamp = Zotero_DB::valueQuery($sql, $libraryIDs[0]);
-
- $sql = "UPDATE shardLibraries SET lastUpdated=FROM_UNIXTIME(?) WHERE libraryID=?";
- foreach ($libraryIDs as $libraryID) {
- Zotero_DB::query(
- $sql,
- array(
- $timestamp,
- $libraryID
- ),
- Zotero_Shards::getByLibraryID($libraryID)
- );
- }
-
- Zotero_DB::commit();
-
- return $timestamp;
- }
-
-
- public static function setTimestampLock($libraryIDs, $timestamp) {
- $fail = false;
-
- for ($i=0, $len=sizeOf($libraryIDs); $i<$len; $i++) {
- $libraryID = $libraryIDs[$i];
- if (!Z_Core::$MC->add("libraryTimestampLock_" . $libraryID . "_" . $timestamp, 1, 60)) {
- $fail = true;
- break;
- }
- }
-
- if ($fail) {
- if ($i > 0) {
- for ($j=$i-1; $j>=0; $j--) {
- $libraryID = $libraryIDs[$i];
- Z_Core::$MC->delete("libraryTimestampLock_" . $libraryID . "_" . $timestamp);
- }
- }
- return false;
- }
-
- return true;
- }
-
-
- /**
- * Get library version from the database
- */
- public static function getVersion($libraryID) {
- // Default empty library
- if ($libraryID === 0) return 0;
-
- $sql = "SELECT version FROM shardLibraries WHERE libraryID=?";
- $version = Zotero_DB::valueQuery(
- $sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID)
- );
-
- // TEMP: Remove after classic sync, and use shardLibraries only for version info?
- if (!$version || $version == 1) {
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- $readOnly = Zotero_DB::isReadOnly($shardID);
-
- $sql = "SELECT lastUpdated, version FROM libraries WHERE libraryID=?";
- $row = Zotero_DB::rowQuery($sql, $libraryID);
-
- $sql = "UPDATE shardLibraries SET version=?, lastUpdated=? WHERE libraryID=?";
- Zotero_DB::query(
- $sql,
- array($row['version'], $row['lastUpdated'], $libraryID),
- $shardID,
- [
- 'writeInReadMode' => true
- ]
- );
- $sql = "SELECT IFNULL(IF(MAX(version)=0, 1, MAX(version)), 1) FROM items WHERE libraryID=?";
- $version = Zotero_DB::valueQuery($sql, $libraryID, $shardID);
-
- $sql = "UPDATE shardLibraries SET version=? WHERE libraryID=?";
- Zotero_DB::query(
- $sql,
- [
- $version,
- $libraryID
- ],
- $shardID,
- [
- 'writeInReadMode' => true
- ]
- );
- }
-
- // Store original version for use by getOriginalVersion()
- if (!isset(self::$originalVersions[$libraryID])) {
- self::$originalVersions[$libraryID] = $version;
- }
- return $version;
- }
-
-
- /**
- * Get the first library version retrieved during this request, or the
- * database version if none
- *
- * Since the library version is updated at the start of a request,
- * but write operations may cache data before making changes, the
- * original, pre-update version has to be used in cache keys.
- * Otherwise a subsequent request for the new library version might
- * omit data that was written with that version. (The new data can't
- * just be written with the same version because a cache write
- * could fail.)
- */
- public static function getOriginalVersion($libraryID) {
- if (isset(self::$originalVersions[$libraryID])) {
- return self::$originalVersions[$libraryID];
- }
- $version = self::getVersion($libraryID);
- self::$originalVersions[$libraryID] = $version;
- return $version;
- }
-
-
- /**
- * Get the latest library version set during this request, or the original
- * version if none
- */
- public static function getUpdatedVersion($libraryID) {
- if (isset(self::$updatedVersions[$libraryID])) {
- return self::$updatedVersions[$libraryID];
- }
- return self::getOriginalVersion($libraryID);
- }
-
-
- public static function updateVersion($libraryID) {
- self::getOriginalVersion($libraryID);
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- $sql = "UPDATE shardLibraries SET version=LAST_INSERT_ID(version+1)
- WHERE libraryID=?";
- Zotero_DB::query($sql, $libraryID, $shardID);
- $version = Zotero_DB::valueQuery("SELECT LAST_INSERT_ID()", false, $shardID);
- // Store new version for use by getUpdatedVersion()
- self::$updatedVersions[$libraryID] = $version;
- return $version;
- }
-
-
- public static function isLocked($libraryID) {
- $sql = "SELECT COUNT(*) FROM syncUploadQueueLocks WHERE libraryID=?";
- if (Zotero_DB::valueQuery($sql, $libraryID)) {
- return true;
- }
- $sql = "SELECT COUNT(*) FROM syncProcessLocks WHERE libraryID=?";
- return !!Zotero_DB::valueQuery($sql, $libraryID);
- }
-
-
- public static function userCanEdit($libraryID, $userID, $obj=null) {
- $libraryType = Zotero_Libraries::getType($libraryID);
- switch ($libraryType) {
- case 'user':
- case 'publications':
- return $userID == Zotero_Users::getUserIDFromLibraryID($libraryID);
-
- case 'group':
- $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
- $group = Zotero_Groups::get($groupID);
- if (!$group->hasUser($userID) || !$group->userCanEdit($userID)) {
- return false;
- }
-
- if ($obj && $obj instanceof Zotero_Item
- && $obj->isImportedAttachment()
- && !$group->userCanEditFiles($userID)) {
- return false;
- }
- return true;
-
- default:
- throw new Exception("Unsupported library type '$libraryType'");
- }
- }
-
-
- public static function getLastStorageSync($libraryID) {
- $sql = "SELECT UNIX_TIMESTAMP(serverDateModified) AS time FROM items
- JOIN storageFileItems USING (itemID) WHERE libraryID=?
- ORDER BY time DESC LIMIT 1";
- return Zotero_DB::valueQuery(
- $sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID)
- );
- }
-
-
- 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;
- }
-
-
- public static function clearAllData($libraryID) {
- if (empty($libraryID)) {
- throw new Exception("libraryID not provided");
- }
-
- Zotero_DB::beginTransaction();
-
- $tables = array(
- 'collections', 'creators', 'items', 'relations', 'savedSearches', 'tags',
- 'syncDeleteLogIDs', 'syncDeleteLogKeys', 'settings'
- );
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- self::deleteCachedData($libraryID);
-
- // Because of the foreign key constraint on the itemID, delete MySQL full-text rows
- // first, and then clear from Elasticsearch below
- Zotero_FullText::deleteByLibraryMySQL($libraryID);
-
- foreach ($tables as $table) {
- // Delete notes and attachments first (since they may be child items)
- if ($table == 'items') {
- $sql = "DELETE FROM $table WHERE libraryID=? AND itemTypeID IN (1,14)";
- Zotero_DB::query($sql, $libraryID, $shardID);
- }
-
- try {
- $sql = "DELETE FROM $table WHERE libraryID=?";
- Zotero_DB::query($sql, $libraryID, $shardID);
- }
- catch (Exception $e) {
- // ON DELETE CASCADE will only go 15 levels deep, so if we get an FK error, try
- // deleting subcollections first, starting with the most recent, which isn't foolproof
- // but will probably almost always do the trick.
- if ($table == 'collections'
- && strpos($e->getMessage(), "Cannot delete or update a parent row") !== false) {
- $sql = "DELETE FROM collections WHERE libraryID=? "
- . "ORDER BY parentCollectionID IS NULL, collectionID DESC";
- Zotero_DB::query($sql, $libraryID, $shardID);
- }
- else {
- throw $e;
- }
- }
- }
-
- Zotero_FullText::deleteByLibrary($libraryID);
-
- self::updateVersion($libraryID);
- self::updateTimestamps($libraryID);
-
- Zotero_Notifier::trigger("clear", "library", $libraryID);
-
- Zotero_DB::commit();
- }
-
-
-
- /**
- * Delete data from memcached
- */
- public static function deleteCachedData($libraryID) {
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- // Clear itemID-specific memcache values
- $sql = "SELECT itemID FROM items WHERE libraryID=?";
- $itemIDs = Zotero_DB::columnQuery($sql, $libraryID, $shardID);
- if ($itemIDs) {
- $cacheKeys = array(
- "itemCreators",
- "itemIsDeleted",
- "itemRelated",
- "itemUsedFieldIDs",
- "itemUsedFieldNames"
- );
- foreach ($itemIDs as $itemID) {
- foreach ($cacheKeys as $key) {
- Z_Core::$MC->delete($key . '_' . $itemID);
- }
- }
- }
-
- /*foreach (Zotero_DataObjects::$objectTypes as $type=>$arr) {
- $className = "Zotero_" . $arr['plural'];
- call_user_func(array($className, "clearPrimaryDataCache"), $libraryID);
- }*/
- }
-}
-?>
diff --git a/model/Notes.inc.php b/model/Notes.inc.php
deleted file mode 100644
index d1701b1a..00000000
--- a/model/Notes.inc.php
+++ /dev/null
@@ -1,180 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Notes {
- public static $MAX_TITLE_LENGTH = 79;
- public static $MAX_NOTE_LENGTH = 250000;
-
- private static $noteCache = array();
- private static $hashCache = array();
-
-
- public static function getCachedNote($libraryID, $itemID) {
- if (!$libraryID) {
- throw new Exception("Library ID not provided");
- }
- if (!$itemID) {
- throw new Exception("Item ID not provided");
- }
- return isset(self::$noteCache[$libraryID][$itemID]) ? self::$noteCache[$libraryID][$itemID] : false;
- }
-
-
- public static function updateNoteCache($libraryID, $itemID, $note) {
- if (!$libraryID) {
- throw new Exception("Library ID not provided");
- }
- if (!$itemID) {
- throw new Exception("Item ID not provided");
- }
- if (!isset(self::$noteCache[$libraryID])) {
- self::$noteCache[$libraryID] = array();
- }
- self::$noteCache[$libraryID][$itemID] = $note;
- }
-
-
- public static function getHash($libraryID, $itemID) {
- if (!isset(self::$hashCache[$libraryID])) {
- self::loadHashes($libraryID);
- }
- if (!empty(self::$hashCache[$libraryID][$itemID])) {
- return self::$hashCache[$libraryID][$itemID];
- }
- return false;
- }
-
-
- public static function updateHash($libraryID, $itemID, $value) {
- if (!isset(self::$hashCache[$libraryID])) {
- self::$hashCache[$libraryID] = array();
- }
-
- if ($value) {
- self::$hashCache[$libraryID][$itemID] = $value;
- }
- else {
- unset(self::$hashCache[$libraryID][$itemID]);
- }
- }
-
-
- public static function sanitize($text) {
- if (strlen(trim($text)) == 0) {
- return $text;
- }
-
- $url = Z_CONFIG::$HTMLCLEAN_SERVER_URL;
-
- $start = microtime(true);
-
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $text);
- curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: text/plain"]);
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 2);
- curl_setopt($ch, CURLOPT_HEADER, 0); // do not return HTTP headers
- curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
- $response = curl_exec($ch);
-
- $time = microtime(true) - $start;
- StatsD::timing("api.htmlclean", $time * 1000);
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
-
- if ($code != 200) {
- throw new Exception($code . " from htmlclean "
- . "[URL: '$url'] [INPUT: '" . Zotero_Utilities::ellipsize($text, 100)
- . "'] [RESPONSE: '$response']");
- }
-
- if (!$response) {
- error_log($code);
- error_log($time);
- error_log($url);
- }
-
- if (!$response) {
- throw new Exception("Error cleaning note");
- }
-
- return $response;
- }
-
-
- /**
- * Return first line (or first MAX_LENGTH characters) of note content
- *
- * input should be sanitized already
- */
- public static function noteToTitle($text, $ignoreNewline=false) {
- if (!$text) {
- return '';
- }
- $max = self::$MAX_TITLE_LENGTH;
-
- // Get a reasonable beginning to work with
- $text = mb_substr($text, 0, $max * 5);
-
- // Clean and unencode
- $text = preg_replace("/<\/p>[\s]*/", "
\n", $text);
- $text = strip_tags($text);
- $text = html_entity_decode($text, ENT_COMPAT, 'UTF-8');
-
- $t = mb_strcut($text, 0, $max);
- if ($ignoreNewline) {
- $t = preg_replace('/\s+/', ' ', $t);
- }
- else {
- $ln = mb_strpos($t, "\n");
- if ($ln !== false && $ln < $max) {
- $t = mb_strcut($t, 0, $ln);
- }
- }
- return $t;
- }
-
-
- public static function loadHashes($libraryID) {
- $sql = "SELECT itemID, hash FROM itemNotes JOIN items USING (itemID) WHERE libraryID=?";
- $hashes = Zotero_DB::query($sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID));
- if (!$hashes) {
- return;
- }
-
- if (!isset(self::$hashCache[$libraryID])) {
- self::$hashCache[$libraryID] = array();
- }
-
- foreach ($hashes as $hash) {
- if ($hash['hash']) {
- self::$hashCache[$libraryID][$hash['itemID']] = $hash['hash'];
- }
- }
- }
-}
-?>
diff --git a/model/Notifier.inc.php b/model/Notifier.inc.php
deleted file mode 100644
index e354cc45..00000000
--- a/model/Notifier.inc.php
+++ /dev/null
@@ -1,292 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Notifier {
- private static $locked = false;
- private static $queue = array();
- private static $inTransaction = false;
- private static $observers = array();
-
- private static $types = array(
- 'apikey-library', 'collection', 'creator', 'search', 'share', 'share-items', 'item',
- 'collection-item', 'item-tag', 'tag', 'group', 'trash', 'relation',
- 'library', 'publications'
- );
-
- /**
- * @param $observer Class Class with method notify($event, $type, $ids, $extraData)
- * @param $types [array] Types to receive notications for
- * @param $label [string] Name of the observer
- */
- public static function registerObserver($observer, $types=[], $name='') {
- if (is_scalar($types)) {
- $types = array($types);
- }
- foreach ($types as $type) {
- if (!in_array($type, self::$types)) {
- throw new Exception("Invalid type '$type'");
- }
- }
-
- if (empty($name) || isset(self::$observers[$name])) {
- $len = 2;
- $tries = 10;
- do {
- // Increase the hash length if we can't find a unique key
- if (!$tries) {
- $len++;
- $tries = 5;
- }
-
- $key = strlen($name) ? "$name-" : '';
- $key .= Zotero_Utilities::randomString($len, 'mixed');
- $tries--;
- }
- while (isset(self::$observers[$key]));
- }
- else {
- $key = $name;
- }
-
- Z_Core::debug('Registering observer for '
- . ($types ? '[' . implode(",", $types) . ']' : 'all types')
- . ' in notifier ' . $key . "'", 4);
- self::$observers[$key] = array(
- "observer" => $observer,
- "types" => $types
- );
- return $key;
- }
-
-
- public static function unregisterObserver($key) {
- Z_Core::debug("Unregistering observer in notifier '$key'", 4);
- unset(self::$observers[$key]);
- }
-
-
- /**
- * Trigger a notification to the appropriate observers
- *
- * Possible values:
- *
- * event: 'add', 'modify', 'delete', 'move' ('c', for changing parent),
- * 'remove' (ci, it), 'refresh', 'redraw', 'trash'
- * type - 'collection', 'search', 'item', 'collection-item', 'item-tag', 'tag', 'group', 'relation'
- * ids - single id or array of ids
- *
- * Notes:
- *
- * - If event queuing is on, events will not fire until commit() is called
- * unless $force is true.
- *
- * - New events and types should be added to the order arrays in commit()
- **/
- public static function trigger($event, $type, $ids, $extraData=null, $force=false) {
- if (!in_array($type, self::$types)) {
- throw new Exception("Invalid type '$type'");
- }
-
- if (is_scalar($ids)) {
- $ids = array($ids);
- }
-
- $queue = self::$inTransaction && !$force;
-
- Z_Core::debug("Notifier trigger('$event', '$type', [" . implode(",", $ids) . '])'
- . ($queue ? " queued" : " called " . "[observers: " . sizeOf(self::$observers) . "]"));
-
- // Merge with existing queue
- if ($queue) {
- if (!isset(self::$queue[$type])) {
- self::$queue[$type] = array();
- }
- if (!isset(self::$queue[$type][$event])) {
- self::$queue[$type][$event] = array(
- "ids" => array(),
- "data" => array()
- );
- }
-
- // Merge ids
- self::$queue[$type][$event]['ids'] = array_merge(
- self::$queue[$type][$event]['ids'], $ids
- );
-
- // Merge extraData keys
- if ($extraData) {
- foreach ($extraData as $dataID => $data) {
- self::$queue[$type][$event]['data'][$dataID] = $data;
- }
- }
-
- return true;
- }
-
- foreach (self::$observers as $hash => $observer) {
- // Find observers that handle notifications for this type (or all types)
- if (!$observer['types'] || in_array($type, $observer['types'])) {
- Z_Core::debug("Calling notify('$event', '$type', ...) on observer with hash '$hash'", 4);
-
- // Catch exceptions so all observers get notified even if
- // one throws an error
- try {
- call_user_func_array(
- array($observer['observer'], "notify"),
- array($event, $type, $ids, $extraData)
- );
- }
- catch (Exception $e) {
- error_log("WARNING: $e");
- }
- }
- }
-
- return true;
- }
-
-
- /*
- * Begin queueing event notifications (i.e., don't notify the observers)
- *
- * $lock will prevent subsequent commits from running the queue until
- * commit() is called with $unlock set to true
- */
- public static function begin($lock=false) {
- if ($lock && !self::$locked) {
- self::$locked = true;
- $unlock = true;
- }
- else {
- $unlock = false;
- }
-
- if (self::$inTransaction) {
- //Zotero.debug("Notifier queue already open", 4);
- }
- else {
- Z_Core::debug("Beginning Notifier event queue");
- self::$inTransaction = true;
- }
-
- return $unlock;
- }
-
-
- /*
- * Send notifications for ids in the event queue
- *
- * If the queue is locked, notifications will only run if $unlock is true
- */
- public static function commit($unlock=null) {
- if (!self::$queue) {
- self::reset();
- return;
- }
-
- // If there's a lock on the event queue and $unlock isn't given, don't commit
- if (($unlock === null && self::$locked) || $unlock === false) {
- //Zotero.debug("Keeping Notifier event queue open", 4);
- return;
- }
-
- $runQueue = array();
-
- $order = array(
- 'library',
- 'collection',
- 'search',
- 'item',
- 'collection-item',
- 'item-tag',
- 'tag'
- );
- uasort(self::$queue, function ($a, $b) use ($order) {
- return array_search($b, $order) - array_search($a, $order);
- });
-
- $order = array('add', 'modify', 'remove', 'move', 'delete', 'trash');
- $totals = '';
- foreach (array_keys(self::$queue) as $type) {
- if (!isset($runQueue[$type])) {
- $runQueue[$type] = array();
- }
-
- asort(self::$queue[$type]);
-
- foreach (self::$queue[$type] as $event => $obj) {
- $runObj = array(
- 'ids' => array(),
- 'data' => array()
- );
-
- // Remove redundant ids
- for ($i = 0, $len = sizeOf($obj['ids']); $i < $len; $i++) {
- $id = $obj['ids'][$i];
- $data = isset($obj['data'][$id]) ? $obj['data'][$id] : null;
-
- if (!in_array($id, $runObj['ids'])) {
- $runObj['ids'][] = $id;
- $runObj['data'][$id] = $data;
- }
- }
-
- if ($runObj['ids'] || $event == 'refresh') {
- $totals .= " [$event-$type: " . sizeOf($runObj['ids']) . "]";
- }
-
- $runQueue[$type][$event] = $runObj;
- }
- }
-
- self::reset();
-
- if ($totals) {
- Z_Core::debug("Committing Notifier event queue" . $totals);
-
- foreach (array_keys($runQueue) as $type) {
- foreach ($runQueue[$type] as $event => $obj) {
- if (sizeOf($obj['ids']) || $event == 'refresh') {
- self::trigger(
- $event, $type, $obj['ids'], $obj['data'], true
- );
- }
- }
- }
- }
- }
-
-
- /*
- * Reset the event queue
- */
- public static function reset() {
- Z_Core::debug("Resetting Notifier event queue");
- self::$locked = false;
- self::$queue = array();
- self::$inTransaction = false;
- }
-}
diff --git a/model/NotifierObserver.inc.php b/model/NotifierObserver.inc.php
deleted file mode 100644
index f8189a7a..00000000
--- a/model/NotifierObserver.inc.php
+++ /dev/null
@@ -1,162 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_NotifierObserver {
- private static $messageReceivers = [];
- private static $continued = false;
-
- public static function init($messageReceiver=null) {
- Zotero_Notifier::registerObserver(
- __CLASS__,
- ["library", "publications", "apikey-library"],
- "NotifierObserver"
- );
-
- // Send notifications to Redis by default
- self::$messageReceivers[] = function ($topic, $message) {
- $redis = Z_Redis::get('notifications');
- if (!$redis) {
- Z_Core::logError('Error: Failed to get Redis client for notifications');
- return;
- };
- $redis->publish($topic, $message);
- };
- }
-
- public static function setContinued() {
- self::$continued = true;
- }
-
- public static function addMessageReceiver($messageReceiver) {
- self::$messageReceivers[] = $messageReceiver;
- }
-
-
- public static function notify($event, $type, $ids, $extraData) {
- if ($type == "library" || $type == "publications") {
- switch ($event) {
- case "modify":
- $event = "topicUpdated";
- break;
-
- case "delete":
- $event = "topicDeleted";
- break;
-
- default:
- return;
- }
-
- 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)
- );
- if ($type == 'publications') {
- $topic .= '/publications';
- }
- }
- // For deleted libraries (groups), the URI-based method fails,
- // so just build from parts
- else {
- if (!isset($extraData) || !isset($extraData[$libraryID])) {
- error_log("Extra data isn't set for $event");
- continue;
- }
- // /groups/1234
- $topic = '/' . $extraData[$libraryID]['type'] . "s/"
- . $extraData[$libraryID]['libraryTypeID'];
- }
- $message = [
- "event" => $event,
- "topic" => $topic
- ];
-
- if (self::$continued) {
- $message['continued'] = true;
- }
-
- if (!empty($extraData[$id])) {
- foreach ($extraData[$id] as $key => $val) {
- $message[$key] = $val;
- }
- }
- self::send($topic, $message);
- }
- }
- else if ($type == "apikey-library") {
- switch ($event) {
- case "add":
- $event = "topicAdded";
- break;
-
- case "remove":
- $event = "topicRemoved";
- break;
-
- default:
- return;
- }
-
- foreach ($ids as $id) {
- list($apiKeyID, $libraryID) = explode("-", $id);
- // Get topic from URI
- $topic = str_replace(
- Zotero_URI::getBaseURI(), "/", Zotero_URI::getLibraryURI($libraryID)
- );
- $message = [
- "event" => $event,
- "apiKeyID" => $apiKeyID,
- "topic" => $topic
- ];
-
- if (self::$continued) {
- $message['continued'] = true;
- }
-
- if (!empty($extraData[$id])) {
- foreach ($extraData[$id] as $key => $val) {
- $message[$key] = $val;
- }
- }
-
- self::send("api-key:$apiKeyID", $message);
- }
- }
- }
-
- private static function send($channel, $message) {
- foreach (self::$messageReceivers as $receiver) {
- $receiver(
- $channel,
- json_encode($message, JSON_UNESCAPED_SLASHES)
- );
- }
- }
-}
diff --git a/model/Permissions.inc.php b/model/Permissions.inc.php
deleted file mode 100644
index 83ffe8b2..00000000
--- a/model/Permissions.inc.php
+++ /dev/null
@@ -1,298 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Permissions {
- private $super = false;
- private $anonymous = false;
- private $publications = false;
- private $userID = null;
- private $permissions = array();
- private $userPrivacy = array();
- private $groupPrivacy = array();
-
-
- public function __construct($userID=null) {
- $this->userID = $userID;
- }
-
-
- public function canAccess($libraryID, $permission='library') {
- if ($this->super) {
- return true;
- }
-
- if (!$libraryID) {
- throw new Exception('libraryID not provided');
- }
-
- // TEMP: necessary?
- $libraryID = (int) $libraryID;
-
- // If requested permission is explicitly set
- //
- // This assumes that permissions can't be incorrectly set
- // (e.g., are properly removed when a user loses group access)
- if (!empty($this->permissions[$libraryID][$permission])) {
- return true;
- }
-
- $libraryType = Zotero_Libraries::getType($libraryID);
- switch ($libraryType) {
- case 'user':
- $userID = Zotero_Users::getUserIDFromLibraryID($libraryID);
- $privacy = $this->getUserPrivacy($userID);
- break;
-
- // TEMP
- case 'publications':
- return true;
-
- case 'group':
- $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
-
- // If key has access to all groups, grant access if user
- // has read access to group
- if (!empty($this->permissions[0]['library'])) {
- $group = Zotero_Groups::get($groupID);
-
- // Only members have file access
- if ($permission == 'files') {
- return !!$group->getUserRole($this->userID);
- }
-
- if ($group->userCanRead($this->userID)) {
- return true;
- }
- }
-
- $privacy = $this->getGroupPrivacy($groupID);
- break;
-
- default:
- throw new Exception("Unsupported library type '$libraryType'");
- }
-
- switch ($permission) {
- case 'view':
- return $privacy['view'];
-
- case 'library':
- return $privacy['library'];
-
- case 'notes':
- return $privacy['notes'];
-
- default:
- return false;
- }
- }
-
-
- public function canAccessObject(Zotero_DataObject $obj) {
- if ($obj instanceof Zotero_Item && $this->publications && $obj->inPublications) {
- return true;
- }
-
- $scope = 'library';
- if ($obj instanceof Zotero_Item && $obj->isNote()) {
- $scope = 'notes';
- }
- return $this->canAccess($obj->libraryID, $scope);
- }
-
-
- /**
- * This should be called after canAccess()
- */
- public function canWrite($libraryID) {
- if ($this->super) {
- return true;
- }
-
- if ($libraryID === 0) {
- return false;
- }
-
- if (!$libraryID) {
- throw new Exception('libraryID not provided');
- }
-
- if (!empty($this->permissions[$libraryID]['write'])) {
- return true;
- }
-
- $libraryType = Zotero_Libraries::getType($libraryID);
- switch ($libraryType) {
- case 'user':
- return false;
-
- // Write permissions match key's write access to user library
- case 'publications':
- $userLibraryID = Zotero_Users::getLibraryIDFromUserID($this->userID);
- return $this->canWrite($userLibraryID);
-
- case 'group':
- $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
-
- // If key has write access to all groups, grant access if user
- // has write access to group
- if (!empty($this->permissions[0]['write'])) {
- $group = Zotero_Groups::get($groupID);
- return $group->userCanEdit($this->userID);
- }
-
- return false;
-
- default:
- throw new Exception("Unsupported library type '$libraryType'");
- }
- }
-
-
- public function setPermission($libraryID, $permission, $enabled) {
- if ($this->super) {
- throw new Exception("Super-user permissions already set");
- }
-
- switch ($permission) {
- case 'view':
- case 'library':
- case 'notes':
- case 'files':
- case 'write':
- break;
-
- default:
- throw new Exception("Invalid permission '$permission'");
- }
-
- $this->permissions[$libraryID][$permission] = $enabled;
- }
-
-
- public function setAnonymous() {
- $this->anonymous = true;
- }
-
- public function setPublications() {
- $this->publications = true;
- }
-
- public function setUser($userID) {
- $this->userID = $userID;
- }
-
- public function setSuper() {
- $this->super = true;
- }
-
- public function isSuper() {
- return $this->super;
- }
-
-
- private function getUserPrivacy($userID) {
- if (isset($this->userPrivacy[$userID])) {
- return $this->userPrivacy[$userID];
- }
-
- if (Z_ENV_DEV_SITE) {
- // Hard-coded test values
- $privacy = array();
-
- switch ($userID) {
- case 1:
- $privacy['library'] = true;
- $privacy['notes'] = true;
- break;
-
- case 2:
- $privacy['library'] = false;
- $privacy['notes'] = false;
- break;
-
- default:
- throw new Exception("External requests disabled on dev site");
- }
-
- $this->userPrivacy[$userID] = $privacy;
- return $privacy;
- }
-
- $sql = "SELECT metaKey, metaValue FROM users_meta WHERE userID=? AND metaKey LIKE 'privacy_publish%'";
- try {
- $rows = Zotero_WWW_DB_2::query($sql, $userID);
- }
- catch (Exception $e) {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $rows = Zotero_WWW_DB_1::query($sql, $userID);
- }
-
- $privacy = array(
- 'library' => false,
- 'notes' => false
- );
- foreach ($rows as $row) {
- $privacy[strtolower(substr($row['metaKey'], 15))] = (bool) (int) $row['metaValue'];
- }
- $this->userPrivacy[$userID] = $privacy;
-
- return $privacy;
- }
-
-
- private function getGroupPrivacy($groupID) {
- if (isset($this->groupPrivacy[$groupID])) {
- return $this->groupPrivacy[$groupID];
- }
-
- $group = Zotero_Groups::get($groupID);
- if (!$group) {
- throw new Exception("Group $groupID doesn't exist");
- }
- $privacy = array();
- if ($group->isPublic()) {
- $privacy['view'] = true;
- $privacy['library'] = $group->libraryReading == 'all';
- $privacy['notes'] = $group->libraryReading == 'all';
- }
- else {
- $privacy['view'] = false;
- $privacy['library'] = false;
- $privacy['notes'] = false;
- }
-
- $this->groupPrivacy[$groupID] = $privacy;
-
- return $privacy;
- }
-
-
- public function hasPermission($object, $userID=false) {
- return false;
- }
-}
-?>
diff --git a/model/Processor.inc.php b/model/Processor.inc.php
deleted file mode 100644
index e6206b35..00000000
--- a/model/Processor.inc.php
+++ /dev/null
@@ -1,153 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-abstract class Zotero_Processor {
- protected $id;
-
- public function run($id=null) {
- $this->id = $id;
- $this->addr = gethostbyname(gethostname());
-
- if (!Z_CONFIG::$PROCESSORS_ENABLED) {
- $sleep = 20;
- $this->log("Processors disabled — exiting in $sleep seconds");
- sleep($sleep);
- try {
- $this->notifyProcessor("LOCK" . " " . $id);
- }
- catch (Exception $e) {
- $this->log($e);
- }
- return;
- }
-
- $this->log("Starting sync processor");
-
- $startTime = microtime(true);
- try {
- $processed = $this->processFromQueue();
- if (Zotero_DB::transactionInProgress()) {
- error_log("WARNING: Transaction still in progress after processing!");
- }
- }
- catch (Exception $e) {
- $this->log($e);
- throw ($e);
- }
-
- $duration = microtime(true) - $startTime;
-
- $error = false;
-
- // Success
- if ($processed == 1) {
- $this->log("Process completed in " . round($duration, 2) . " seconds");
- $signal = "DONE";
- }
- else if ($processed == 0) {
- $this->log("Exiting with no processes found");
- $signal = "NONE";
- }
- else if ($processed == -1) {
- $this->log("Exiting on lock error");
- $signal = "LOCK";
- }
- else {
- $this->log("Exiting on error");
- $signal = "ERROR";
- }
-
- if ($id) {
- try {
- $this->notifyProcessor($signal . " " . $id);
- }
- catch (Exception $e) {
- $this->log($e);
- }
- }
- }
-
-
- protected function notifyProcessor($signal) {
- Zotero_Processors::notifyProcessor($this->mode, $signal, $this->addr, $this->port);
- }
-
-
- private function log($msg) {
- $targetVariable = "PROCESSOR_LOG_TARGET_" . strtoupper($this->mode);
- Z_Log::log(Z_CONFIG::$$targetVariable, "[" . $this->id . "] $msg");
- }
-
-
- //
- // Abstract methods
- //
- abstract protected function processFromQueue();
-}
-
-class Zotero_Download_Processor extends Zotero_Processor {
- protected $mode = 'download';
-
- public function __construct() {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_DOWNLOAD;
- }
-
- protected function processFromQueue() {
- return Zotero_Sync::processDownloadFromQueue($this->id);
- }
-}
-
-class Zotero_Upload_Processor extends Zotero_Processor {
- protected $mode = 'upload';
-
- public function __construct() {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_UPLOAD;
- }
-
- protected function processFromQueue() {
- return Zotero_Sync::processUploadFromQueue($this->id);
- }
-}
-
-class Zotero_Error_Processor extends Zotero_Processor {
- protected $mode = 'error';
-
- public function __construct() {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_ERROR;
- }
-
- protected function processFromQueue() {
- return Zotero_Sync::checkUploadForErrors($this->id);
- }
-
- protected function notifyProcessor($signal) {
- // Tell the upload processor a process is available
- Zotero_Processors::notifyProcessors('upload', $signal);
-
- Zotero_Processors::notifyProcessor($this->mode, $signal, $this->addr, $this->port);
- }
-}
-?>
diff --git a/model/ProcessorDaemon.inc.php b/model/ProcessorDaemon.inc.php
deleted file mode 100644
index f00cbbdb..00000000
--- a/model/ProcessorDaemon.inc.php
+++ /dev/null
@@ -1,402 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-abstract class Zotero_Processor_Daemon {
- // Default is no concurrency
- private $maxProcessors = 1;
- private $minPurgeInterval = 20; // minimum time between purging orphaned processors
- private $minCheckInterval = 15; // minimum time between checking queued processes on NEXT
- private $lockWait = 2; // Delay when a processor returns a LOCK signal
-
- private $hostname;
- private $addr;
-
- private $processors = array();
- private $queuedProcesses = 0;
-
- // Set by implementors
- protected $mode;
- protected $port;
-
- public function __construct($config=array()) {
- error_reporting(E_ALL | E_STRICT);
- set_time_limit(0);
-
- $this->hostname = gethostname();
- $this->addr = gethostbyname($this->hostname);
-
- // Set configuration parameters
- foreach ($config as $key => $val) {
- switch ($key) {
- case 'maxProcessors':
- case 'minPurgeInterval':
- case 'minCheckInterval':
- case 'lockWait':
- $this->$key = $val;
- break;
-
- default:
- throw new Exception("Invalid configuration key '$key'");
- }
- }
- }
-
-
- public function run() {
- // Catch TERM and unregister from the database
- //pcntl_signal(SIGTERM, array($this, 'handleSignal'));
- //pcntl_signal(SIGINT, array($this, 'handleSignal'));
-
- $this->log("Starting " . $this->mode . " processor daemon");
- $this->register();
-
- // Bind
- $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- $success = socket_bind($socket, $this->addr, $this->port);
- if (!$success) {
- $code = socket_last_error($socket);
- $this->unregister();
- die(socket_strerror($code));
- }
-
- $buffer = 'GO';
- $mode = null;
-
- $first = true;
- $lastPurge = 0;
-
- do {
- if ($first) {
- $first = false;
- }
- else {
- //$this->log("Waiting for command");
- $from = '';
- $port = 0;
- socket_recvfrom($socket, $buffer, 32, MSG_WAITALL, $from, $port);
- }
-
- //pcntl_signal_dispatch();
-
- // Processor return value
- if (preg_match('/^(DONE|NONE|LOCK|ERROR) ([0-9]+)/', $buffer, $return)) {
- $signal = $return[1];
- $id = $return[2];
-
- $this->removeProcessor($id);
-
- if ($signal == "DONE" || $signal == "ERROR") {
-
- }
- else if ($signal == "NONE") {
- continue;
- }
- else if ($signal == "LOCK") {
- $this->log("LOCK received — waiting " . $this->lockWait . " second" . $this->pluralize($this->lockWait));
- sleep($this->lockWait);
- }
-
- $buffer = "GO";
- }
-
- if ($buffer == "NEXT" || $buffer == "GO") {
- if ($lastPurge == 0) {
- $lastPurge = microtime(true);
- }
- // Only purge processors if enough time has passed since last check
- else if ((microtime(true) - $lastPurge) >= $this->minPurgeInterval) {
- $purged = $this->purgeProcessors();
- $this->log("Purged $purged lost processor" . $this->pluralize($purged));
- $purged = $this->purgeOldProcesses();
- $this->log("Purged $purged old process" . $this->pluralize($purged, "es"));
- $lastPurge = microtime(true);
- }
-
- $numProcessors = $this->countProcessors();
-
- if ($numProcessors >= $this->maxProcessors) {
- //$this->log("Already at max " . $this->maxProcessors . " processors");
- continue;
- }
-
- try {
- $queuedProcesses = $this->countQueuedProcesses();
-
- $this->log($numProcessors . " processor" . $this->pluralize($numProcessors) . ", "
- . $queuedProcesses . " queued process" . $this->pluralize($queuedProcesses, "es"));
-
- // Nothing queued, so go back and wait
- if (!$queuedProcesses) {
- continue;
- }
-
- // Wanna be startin' somethin'
- $maxToStart = $this->maxProcessors - $numProcessors;
- if ($queuedProcesses > $maxToStart) {
- $toStart = $maxToStart;
- }
- else {
- $toStart = 1;
- }
-
- if ($toStart <= 0) {
- $this->log("No processors to start");
- continue;
- }
-
- $this->log("Starting $toStart new processor" . $this->pluralize($toStart));
-
- // Start new processors
- for ($i=0; $i<$toStart; $i++) {
- $id = Zotero_ID::getBigInt();
- $pid = shell_exec(
- Z_CONFIG::$CLI_PHP_PATH . " " . Z_ENV_BASE_PATH . "processor/"
- . $this->mode . "/processor.php $id > /dev/null & echo $!"
- );
- $this->processors[$id] = $pid;
- }
- }
- catch (Exception $e) {
- // If lost connection to MySQL, exit so we can be restarted
- if (strpos($e->getMessage(), "MySQL server has gone away") === 0) {
- $this->log($e);
- $this->log("Lost connection to DB — exiting");
- exit;
- }
-
- $this->log($e);
- }
- }
- }
- while ($buffer != 'QUIT');
-
- $this->log("QUIT received — exiting");
- $this->unregister();
- }
-
-
- public function handleSignal($sig) {
- $signal = $sig;
- switch ($sig) {
- case 2:
- $signal = 'INT';
- break;
-
- case 15:
- $signal = 'TERM';
- break;
- }
- $this->log("Got $signal signal — exiting");
- $this->unregister();
- exit;
- }
-
-
- private function register() {
- Zotero_Processors::register($this->mode, $this->addr, $this->port);
- }
-
-
- private function unregister() {
- Zotero_Processors::unregister($this->mode, $this->addr, $this->port);
- }
-
-
- private function countProcessors() {
- return sizeOf($this->processors);
- }
-
-
- private function removeProcessor($id) {
- if (!isset($this->processors[$id])) {
- //$this->log("Process $id not found for removal");
- }
- else {
- unset($this->processors[$id]);
- }
- }
-
-
- private function purgeProcessors() {
- $ids = array_keys($this->processors);
- $purged = 0;
- foreach ($ids as $id) {
- if (!$this->isRunning($this->processors[$id])) {
- $this->log("Purging lost processor $id");
- unset($this->processors[$id]);
- $this->removeProcess($id);
- $purged++;
- }
- }
- return $purged;
- }
-
-
- /**
- * Remove process id from any processes in DB that we have no record of
- * (e.g., started in a previous daemon session) and that are no longer running
- */
- private function purgeOldProcesses() {
- $processes = $this->getOldProcesses($this->hostname);
- if (!$processes) {
- return 0;
- }
-
- $removed = 0;
- foreach ($processes as $id) {
- if (isset($this->processors[$id])) {
- continue;
- }
- // Check if process is running
- if (!$this->isRunningByID($id)) {
- $this->log("Purging lost process $id");
- $this->removeProcess($id);
- $removed++;
- }
- }
- return $removed;
- }
-
-
- private function isRunning($pid) {
- exec("ps $pid", $state);
- return sizeOf($state) >= 2;
- }
-
-
- private function isRunningByID($id) {
- exec("ps | grep $id | grep -v grep", $state);
- return (bool) sizeOf($state);
- }
-
-
- private function pluralize($num, $plural="s") {
- return $num == 1 ? "" : $plural;
- }
-
-
- //
- // Abstract methods
- //
- abstract public function log($msg);
- abstract protected function countQueuedProcesses();
-
- /**
- * Get from the DB any processes that have been running
- * longer than a given period of time
- */
- abstract protected function getOldProcesses($host=null, $seconds=null);
-
- /**
- * Remove process id from DB
- */
- abstract protected function removeProcess($id);
-}
-
-
-class Zotero_Download_Processor_Daemon extends Zotero_Processor_Daemon {
- protected $mode = 'download';
-
- public function __construct($config=array()) {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_DOWNLOAD;
- if (!$config || !isset($config['maxProcessors'])) {
- $config['maxProcessors'] = 3;
- }
- parent::__construct($config);
- }
-
- public function log($msg) {
- Z_Log::log(Z_CONFIG::$PROCESSOR_LOG_TARGET_DOWNLOAD, $msg);
- }
-
- protected function countQueuedProcesses() {
- return Zotero_Sync::countQueuedDownloadProcesses();
- }
-
- protected function getOldProcesses($host=null, $seconds=null) {
- return Zotero_Sync::getOldDownloadProcesses($host, $seconds);
- }
-
- protected function removeProcess($id) {
- Zotero_Sync::removeDownloadProcess($id);
- }
-}
-
-
-class Zotero_Upload_Processor_Daemon extends Zotero_Processor_Daemon {
- protected $mode = 'upload';
-
- public function __construct($config=array()) {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_UPLOAD;
- if (!$config || !isset($config['maxProcessors'])) {
- $config['maxProcessors'] = 3;
- }
- parent::__construct($config);
- }
-
- public function log($msg) {
- Z_Log::log(Z_CONFIG::$PROCESSOR_LOG_TARGET_UPLOAD, $msg);
- }
-
- protected function countQueuedProcesses() {
- return Zotero_Sync::countQueuedUploadProcesses();
- }
-
- protected function getOldProcesses($host=null, $seconds=null) {
- return Zotero_Sync::getOldUploadProcesses($host, $seconds);
- }
-
- protected function removeProcess($id) {
- Zotero_Sync::removeUploadProcess($id);
- }
-}
-
-
-class Zotero_Error_Processor_Daemon extends Zotero_Processor_Daemon {
- protected $mode = 'error';
-
- public function __construct($config=array()) {
- $this->port = Z_CONFIG::$PROCESSOR_PORT_ERROR;
- parent::__construct($config);
- }
-
- public function log($msg) {
- Z_Log::log(Z_CONFIG::$PROCESSOR_LOG_TARGET_ERROR, $msg);
- }
-
- protected function countQueuedProcesses() {
- return Zotero_Sync::countQueuedUploadProcesses(true);
- }
-
- protected function getOldProcesses($host=null, $seconds=null) {
- return Zotero_Sync::getOldErrorProcesses($host, $seconds);
- }
-
- protected function removeProcess($id) {
- Zotero_Sync::purgeErrorProcess($id);
- }
-}
-?>
diff --git a/model/Processors.inc.php b/model/Processors.inc.php
deleted file mode 100644
index 05f900f6..00000000
--- a/model/Processors.inc.php
+++ /dev/null
@@ -1,56 +0,0 @@
-
-class Zotero_Processors {
- public static function notifyProcessors($mode, $signal="NEXT") {
- $sql = "SELECT INET_NTOA(addr) AS addr, port FROM processorDaemons WHERE mode=?";
- $daemons = Zotero_DB::query($sql, array($mode));
-
- if (!$daemons) {
- Z_Core::logError("No $mode processor daemons found");
- return;
- }
-
- foreach ($daemons as $daemon) {
- self::notifyProcessor($mode, $signal, $daemon['addr'], $daemon['port']);
- }
- }
-
-
- public static function notifyProcessor($mode, $signal, $addr, $port) {
- switch ($mode) {
- case 'download':
- case 'upload':
- case 'error':
- break;
-
- default:
- throw new Exception("Invalid processor mode '$mode'");
- }
-
- if (!$addr) {
- throw new Exception("Host address not provided");
- }
-
- Z_Core::debug("Notifying $mode processor $addr with signal $signal");
-
- $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
- $success = socket_sendto($socket, $signal, strlen($signal), MSG_EOF, $addr, $port);
- if (!$success) {
- $code = socket_last_error($socket);
- throw new Exception(socket_strerror($code));
- }
- }
-
-
- public static function register($mode, $addr, $port) {
- $sql = "INSERT INTO processorDaemons (mode, addr, port) VALUES (?, INET_ATON(?), ?)
- ON DUPLICATE KEY UPDATE port=?, lastSeen=NOW()";
- Zotero_DB::query($sql, array($mode, $addr, $port, $port));
- }
-
-
- public static function unregister($mode, $addr, $port) {
- $sql = "DELETE FROM processorDaemons WHERE mode=? AND addr=INET_ATON(?) AND port=?";
- Zotero_DB::query($sql, array($mode, $addr, $port));
- }
-}
-?>
diff --git a/model/Publications.inc.php b/model/Publications.inc.php
deleted file mode 100644
index 7993774e..00000000
--- a/model/Publications.inc.php
+++ /dev/null
@@ -1,53 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Publications {
- // Currently unused
- private static $rights = [
- 'CC-BY-4.0',
- 'CC-BY-ND-4.0',
- 'CC-BY-NC-SA-4.0',
- 'CC-BY-SA-4.0',
- 'CC-BY-NC-4.0',
- 'CC-BY-NC-ND-4.0',
- 'CC0'
- ];
-
-
- public static function getETag($userID) {
- $cacheKey = "publicationsETag_" . $userID;
- $etag = Z_Core::$MC->get($cacheKey);
- return $etag ? $etag : self::updateETag($userID);
- }
-
-
- public static function updateETag($userID) {
- $cacheKey = "publicationsETag_" . $userID;
- $etag = Zotero_Utilities::randomString(8, 'mixed');
- Z_Core::$MC->set($cacheKey, $etag, 86400);
- return $etag;
- }
-}
diff --git a/model/Relation.inc.php b/model/Relation.inc.php
deleted file mode 100644
index 7f64585a..00000000
--- a/model/Relation.inc.php
+++ /dev/null
@@ -1,256 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Relation {
- private $id;
- private $libraryID;
- private $subject;
- private $predicate;
- private $object;
-
- private $loaded;
-
-
- public function __get($field) {
- if ($this->id && !$this->loaded) {
- $this->load();
- }
- if ($field == 'key') {
- return $this->getKey();
- }
- if (!property_exists('Zotero_Relation', $field)) {
- throw new Exception("Zotero_Relation property '$field' doesn't exist");
- }
- return $this->$field;
- }
-
-
- public function __set($field, $value) {
- switch ($field) {
- case 'libraryID':
- case 'id':
- if ($value == $this->$field) {
- return;
- }
-
- if ($this->loaded) {
- throw new Exception("Cannot set $field after relation is already loaded");
- }
- //$this->checkValue($field, $value);
- $this->$field = $value;
- return;
- }
-
- if ($this->id) {
- if (!$this->loaded) {
- $this->load();
- }
- }
- else {
- $this->loaded = true;
- }
-
- //$this->checkValue($field, $value);
-
- if ($this->$field != $value) {
- //$this->prepFieldChange($field);
- $this->$field = $value;
- }
- }
-
-
- /**
- * Check if search exists in the database
- *
- * @return bool TRUE if the relation exists, FALSE if not
- */
- public function exists() {
- $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
-
- if ($this->id) {
- $sql = "SELECT COUNT(*) FROM relations WHERE relationID=?";
- return !!Zotero_DB::valueQuery($sql, $this->id, $shardID);
- }
-
- if ($this->subject && $this->predicate && $this->object) {
- $sql = "SELECT COUNT(*) FROM relations WHERE libraryID=? AND `key`=?";
- $params = array($this->libraryID, $this->getKey());
- $exists = !!Zotero_DB::valueQuery($sql, $params, $shardID);
-
- // TEMP
- // For linked items, check reverse order too, since client can save in reverse
- // order when an item is dragged from a group to a personal library
- if (!$exists && $this->predicate == Zotero_Relations::$linkedObjectPredicate
- && Zotero_Libraries::getType($this->libraryID) == 'user') {
- $sql = "SELECT COUNT(*) FROM relations WHERE libraryID=? AND `key`=?";
- $params = [
- $this->libraryID,
- Zotero_Relations::makeKey($this->object, $this->predicate, $this->subject)
- ];
- return !!Zotero_DB::valueQuery($sql, $params, $shardID);
- }
-
- return $exists;
- }
-
- throw new Exception("ID or subject/predicate/object not set");
- }
-
-
- /*
- * Save the relation to the DB and return a relationID
- */
- public function save($userID=false) {
- if (!$this->libraryID) {
- trigger_error("Library ID must be set before saving", E_USER_ERROR);
- }
-
- Zotero_Creators::editCheck($this, $userID);
-
- Zotero_DB::beginTransaction();
-
- try {
- $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
-
- $relationID = $this->id ? $this->id : Zotero_ID::get('relations');
-
- Z_Core::debug("Saving relation $relationID");
-
- $sql = "INSERT INTO relations
- (relationID, libraryID, `key`, subject, predicate, object, serverDateModified)
- VALUES (?, ?, ?, ?, ?, ?, ?)";
- $timestamp = Zotero_DB::getTransactionTimestamp();
- $params = array(
- $relationID,
- $this->libraryID,
- $this->getKey(),
- $this->subject,
- $this->predicate,
- $this->object,
- $timestamp
- );
- $insertID = Zotero_DB::query($sql, $params, $shardID);
- if (!$this->id) {
- if (!$insertID) {
- throw new Exception("Relation id not available after INSERT");
- }
- $this->id = $insertID;
- }
-
- // Remove from delete log if it's there
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='relation' AND `key`=?";
- Zotero_DB::query(
- $sql, array($this->libraryID, $this->getKey()), $shardID
- );
-
- Zotero_DB::commit();
- }
- catch (Exception $e) {
- Zotero_DB::rollback();
- throw ($e);
- }
- return $this->id;
- }
-
-
- /**
- * Converts a Zotero_Relation object to a SimpleXMLElement item
- *
- * @return SimpleXMLElement Relation data as SimpleXML element
- */
- public function toXML() {
- if (!$this->loaded) {
- $this->load();
- }
-
- $xml = new SimpleXMLElement(' ');
- $xml['libraryID'] = $this->libraryID;
-
- // Swap dc:replaces for dc:isReplacedBy
- if ($this->predicate == 'dc:replaces') {
- $xml->subject = $this->object;
- $xml->predicate = 'dc:isReplacedBy';
- $xml->object = $this->subject;
- }
- else {
- $xml->subject = $this->subject;
- $xml->predicate = $this->predicate;
- $xml->object = $this->object;
- }
- return $xml;
- }
-
-
- public function toJSON($asArray=false) {
- if (!$this->loaded) {
- $this->load();
- }
-
- $arr = array();
- $arr['subject'] = $this->subject;
- $arr['predicate'] = $this->predicate;
- $arr['object'] = $this->object;
-
- if ($asArray) {
- return $arr;
- }
-
- return Zotero_Utilities::formatJSON($arr);
- }
-
-
- private function load($allowFail=false) {
- if (!$this->libraryID) {
- throw new Exception("Library ID not set");
- }
-
- if (!$this->id) {
- throw new Exception("ID not set");
- }
-
- //Z_Core::debug("Loading data for relation $this->id");
-
- $sql = "SELECT relationID AS id, libraryID, subject, object, predicate FROM relations "
- . "WHERE relationID=?";
- $data = Zotero_DB::rowQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
-
- $this->loaded = true;
-
- if (!$data) {
- return;
- }
-
- foreach ($data as $key=>$val) {
- $this->$key = $val;
- }
- }
-
-
- private function getKey() {
- return Zotero_Relations::makeKey($this->subject, $this->predicate, $this->object);
- }
-}
-?>
diff --git a/model/Relations.inc.php b/model/Relations.inc.php
deleted file mode 100644
index 3d33e358..00000000
--- a/model/Relations.inc.php
+++ /dev/null
@@ -1,343 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Relations extends Zotero_ClassicDataObjects {
- public static $allowedCollectionPredicates = [
- 'owl:sameAs',
- 'mendeleyDB:remoteFolderUUID'
- ];
-
- public static $allowedItemPredicates = [
- 'owl:sameAs',
- 'dc:replaces',
- 'dc:relation',
- 'mendeleyDB:documentUUID',
- 'mendeleyDB:remoteDocumentUUID',
- 'mendeleyDB:fileHash',
- 'mendeleyDB:relatedDocumentUUID',
- 'mendeleyDB:relatedRemoteDocumentUUID',
- 'mendeleyDB:relatedFileHash'
- ];
-
- public static $externalPredicates = [
- 'mendeleyDB:documentUUID',
- 'mendeleyDB:remoteDocumentUUID',
- 'mendeleyDB:fileHash',
- 'mendeleyDB:remoteFolderUUID',
- 'mendeleyDB:relatedDocumentUUID',
- 'mendeleyDB:relatedRemoteDocumentUUID',
- 'mendeleyDB:relatedFileHash'
- ];
-
- protected static $ZDO_object = 'relation';
-
- protected static $primaryFields = array(
- 'id' => 'relationID',
- 'libraryID' => '',
- 'key' => '',
- 'subject' => '',
- 'predicate' => '',
- 'object' => ''
- );
-
- public static $relatedItemPredicate = 'dc:relation';
- public static $linkedObjectPredicate = 'owl:sameAs';
- public static $deletedItemPredicate = 'dc:replaces';
-
- private static $namespaces = array(
- "dc" => 'http://purl.org/dc/elements/1.1/',
- "owl" => 'http://www.w3.org/2002/07/owl#',
- "mendeleyDB" => 'http://zotero.org/namespaces/mendeleyDB#'
- );
-
- public static function get($libraryID, $relationID, $existsCheck=false) {
- $relation = new Zotero_Relation;
- $relation->libraryID = $libraryID;
- $relation->id = $relationID;
- return $relation;
- }
-
-
- /**
- * @return Zotero_Relation[]
- */
- public static function getByURIs($libraryID, $subject=false, $predicate=false, $object=false) {
- if ($predicate) {
- $predicate = implode(':', self::_getPrefixAndValue($predicate));
- }
-
- if (!is_int($libraryID)) {
- throw new Exception('$libraryID must be an integer');
- }
-
- if (!$subject && !$predicate && !$object) {
- throw new Exception("No values provided");
- }
-
- $sql = "SELECT relationID FROM relations WHERE libraryID=?";
- $params = array($libraryID);
- if ($subject) {
- $sql .= " AND subject=?";
- $params[] = $subject;
- }
- if ($predicate) {
- $sql .= " AND predicate=?";
- $params[] = $predicate;
- }
- if ($object) {
- $sql .= " AND object=?";
- $params[] = $object;
- }
- $rows = Zotero_DB::columnQuery(
- $sql, $params, Zotero_Shards::getByLibraryID($libraryID)
- );
- if (!$rows) {
- return array();
- }
-
- $toReturn = array();
- foreach ($rows as $id) {
- $relation = new Zotero_Relation;
- $relation->libraryID = $libraryID;
- $relation->id = $id;
- $toReturn[] = $relation;
- }
- return $toReturn;
- }
-
-
- public static function getSubject($libraryID, $subject=false, $predicate=false, $object=false) {
- $subjects = array();
- $relations = self::getByURIs($libraryID, $subject, $predicate, $object);
- foreach ($relations as $relation) {
- $subjects[] = $relation->subject;
- }
- return $subjects;
- }
-
-
- public static function getObject($libraryID, $subject=false, $predicate=false, $object=false) {
- $objects = array();
- $relations = self::getByURIs($libraryID, $subject, $predicate, $object);
- foreach ($relations as $relation) {
- $objects[] = $relation->object;
- }
- return $objects;
- }
-
-
- public static function makeKey($subject, $predicate, $object) {
- return md5($subject . " " . $predicate . " " . $object);
- }
-
-
- public static function add($libraryID, $subject, $predicate, $object) {
- $predicate = implode(':', self::_getPrefixAndValue($predicate));
-
- $relation = new Zotero_Relation;
- $relation->libraryID = $libraryID;
- $relation->subject = $subject;
- $relation->predicate = $predicate;
- $relation->object = $object;
- $relation->save();
- }
-
-
- public static function eraseByURIPrefix($libraryID, $prefix, $ignorePredicates=false) {
- Zotero_DB.beginTransaction();
-
- $prefix = $prefix . '%';
- $sql = "SELECT relationID FROM relations WHERE libraryID=? AND subject LIKE ?";
- $params = [$libraryID, $prefix];
- if ($ignorePredicates) {
- foreach ($ignorePredicates as $ignorePredicate) {
- $sql .= " AND predicate != ?";
- $params[] = $ignorePredicate;
- }
- }
- $sql .= " UNION SELECT relationID FROM relations WHERE libraryID=? AND object LIKE ?";
- $params = array_merge($params, [$libraryID, $prefix]);
- if ($ignorePredicates) {
- foreach ($ignorePredicates as $ignorePredicate) {
- $sql .= " AND predicate != ?";
- $params[] = $ignorePredicate;
- }
- }
- $ids = Zotero_DB::columnQuery(
- $sql, $params, Zotero_Shards::getByLibraryID($libraryID)
- );
-
- foreach ($ids as $id) {
- $relation = self::get($libraryID, $id);
- Zotero_Relations::delete($libraryID, $relation->key);
- }
-
- Zotero_DB::commit();
- }
-
-
- /**
- * Delete any relations that have the URI as either the subject
- * or the object
- */
- public static function eraseByURI($libraryID, $uri, $ignorePredicates=false) {
- Zotero_DB::beginTransaction();
-
- $sql = "SELECT relationID FROM relations WHERE libraryID=? AND subject=?";
- $params = [$libraryID, $uri];
- if ($ignorePredicates) {
- foreach ($ignorePredicates as $ignorePredicate) {
- $sql .= " AND predicate != ?";
- $params[] = $ignorePredicate;
- }
- }
- $sql .= " UNION SELECT relationID FROM relations WHERE libraryID=? AND object=?";
- $params = array_merge($params, [$libraryID, $uri]);
- if ($ignorePredicates) {
- foreach ($ignorePredicates as $ignorePredicate) {
- $sql .= " AND predicate != ?";
- $params[] = $ignorePredicate;
- }
- }
-
- $ids = Zotero_DB::columnQuery(
- $sql, $params, Zotero_Shards::getByLibraryID($libraryID)
- );
-
- if ($ids) {
- foreach ($ids as $id) {
- $relation = self::get($libraryID, $id);
- Zotero_Relations::delete($libraryID, $relation->key);
- }
- }
-
- Zotero_DB::commit();
- }
-
-
- public static function purge($libraryID) {
- $sql = "SELECT subject FROM relations "
- . "WHERE libraryID=? AND predicate!=? "
- . "UNION "
- . "SELECT object FROM relations "
- . "WHERE libraryID=? AND predicate!=?";
- $uris = Zotero.DB.columnQuery(
- $sql,
- array(
- $libraryID,
- self::$deletedItemPredicate,
- $libraryID,
- self::$deletedItemPredicate
- ),
- Zotero_Shards::getByLibraryID($libraryID)
- );
- if ($uris) {
- $prefix = Zotero_URI::getBaseURI();
- Zotero_DB::beginTransaction();
- foreach ($uris as $uri) {
- // Skip URIs that don't begin with the default prefix,
- // since they don't correspond to local items
- if (strpos($uri, $prefix) === false) {
- continue;
- }
- if (preg_match('/\/items\//', $uri) && !Zotero_URI::getURIItem($uri)) {
- self::eraseByURI($uri);
- }
- if (preg_match('/\/collections\//', $uri) && !Zotero_URI::getURICollection($uri)) {
- self::eraseByURI($uri);
- }
- }
- Zotero_DB::commit();
- }
- }
-
-
- /**
- * Converts a DOMElement item to a Zotero_Relation object
- *
- * @param DOMElement $xml Relation data as DOM element
- * @param Integer $libraryID
- * @return Zotero_Relation Zotero relation object
- */
- public static function convertXMLToRelation(DOMElement $xml, $userLibraryID) {
- $relation = new Zotero_Relation;
- $libraryID = $xml->getAttribute('libraryID');
- if ($libraryID) {
- $relation->libraryID = $libraryID;
- }
- else {
- $relation->libraryID = $userLibraryID;
- }
-
- $subject = $xml->getElementsByTagName('subject')->item(0)->nodeValue;
- $predicate = $xml->getElementsByTagName('predicate')->item(0)->nodeValue;
- $object = $xml->getElementsByTagName('object')->item(0)->nodeValue;
-
- if ($predicate == 'dc:isReplacedBy') {
- $relation->subject = $object;
- $relation->predicate = 'dc:replaces';
- $relation->object = $subject;
- }
- else {
- $relation->subject = $subject;
- $relation->predicate = $predicate;
- $relation->object = $object;
- }
-
- return $relation;
- }
-
-
- /**
- * Converts a Zotero_Relation object to a SimpleXMLElement item
- *
- * @param object $item Zotero_Relation object
- * @return SimpleXMLElement Relation data as SimpleXML element
- */
- public static function convertRelationToXML(Zotero_Relation $relation) {
- return $relation->toXML();
- }
-
-
- private static function _getPrefixAndValue($uri) {
- $parts = explode(':', $uri);
- if (isset($parts[1])) {
- if (!isset(self::$namespaces[$parts[0]])) {
- throw new Exception("Invalid prefix '{$parts[0]}'");
- }
- return $parts;
- }
-
- foreach (self::$namespaces as $prefix => $val) {
- if (strpos($uri, $val) === 0) {
- $value = substr($uri, strlen($val) - 1);
- return array($prefix, $value);
- }
- }
- throw new Exception("Invalid namespace in URI '$uri'");
- }
-}
-?>
diff --git a/model/Results.inc.php b/model/Results.inc.php
deleted file mode 100644
index 3bb3461a..00000000
--- a/model/Results.inc.php
+++ /dev/null
@@ -1,124 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-class Zotero_Results {
- private $requestParams;
- private $successful = [];
- private $success = []; // Deprecated
- private $unchanged = [];
- private $failed = [];
-
- public function __construct(array $requestParams) {
- $this->requestParams = $requestParams;
- }
-
-
- public function addSuccessful($index, $obj) {
- if ($this->requestParams['v'] >= 3) {
- $this->successful[$index] = $obj;
- }
- // Deprecated
- $this->success[$index] = $obj['key'];
- }
-
-
- public function addUnchanged($index, $key) {
- $this->unchanged[$index] = $key;
- }
-
-
- public function addFailure($index, $key, Exception $e) {
- if (isset($this->failed[$index])) {
- throw new Exception("Duplicate index '$index' for failure with key '$key'");
- }
- $this->failed[$index] = Zotero_Errors::parseException($e);
- $this->failed[$index]['key'] = $key;
- return $this->failed[$index];
- }
-
-
- public function generateReport() {
- if ($this->requestParams['v'] >= 3) {
- $report = [
- 'successful' => new stdClass(),
- 'success' => new stdClass(),
- 'unchanged' => new stdClass(),
- 'failed' => new stdClass()
- ];
- }
- else {
- $report = [
- 'success' => new stdClass(),
- 'unchanged' => new stdClass(),
- 'failed' => new stdClass()
- ];
- }
- foreach ($this->successful as $index => $key) {
- $report['successful']->$index = $key;
- }
- // Deprecated
- foreach ($this->success as $index => $key) {
- $report['success']->$index = $key;
- }
- foreach ($this->unchanged as $index => $key) {
- $report['unchanged']->$index = $key;
- }
- foreach ($this->failed as $index => $error) {
- $obj = [
- 'key' => $error['key'],
- 'code' => $error['code'],
- 'message' => htmlspecialchars($error['message'])
- ];
- if (isset($error['data'])) {
- $obj['data'] = $error['data'];
- }
- // If key is blank, don't include it
- if ($obj['key'] === '') {
- unset($obj['key']);
- }
-
- $report['failed']->$index = $obj;
- }
- return $report;
- }
-
-
- public function generateLogMessage() {
- if (!$this->failed) {
- return "";
- }
-
- $str = "";
- foreach ($this->failed as $error) {
- if (!$error['log']) {
- continue;
- }
- $str .= "Code: " . $error['code'] . "\n";
- $str .= "Key: " . $error['key'] . "\n";
- $str .= $error['exception'] . "\n\n";
- }
- return $str;
- }
-}
diff --git a/model/Search.inc.php b/model/Search.inc.php
deleted file mode 100644
index 556de08b..00000000
--- a/model/Search.inc.php
+++ /dev/null
@@ -1,357 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Search extends Zotero_DataObject {
- protected $objectType = 'search';
- protected $dataTypesExtended = ['conditions'];
-
- protected $_name;
- protected $_dateAdded;
- protected $_dateModified;
-
- private $conditions = array();
-
-
- /**
- * Check if search exists in the database
- *
- * @return bool TRUE if the item exists, FALSE if not
- */
- public function exists() {
- if (!$this->id) {
- trigger_error('$this->id not set');
- }
-
- $sql = "SELECT COUNT(*) FROM savedSearches WHERE searchID=?";
- return !!Zotero_DB::valueQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
- }
-
-
- /*
- * Save the search to the DB and return a savedSearchID
- *
- * For new searches, setName() must be called before saving
- */
- public function save($userID=false) {
- if (!$this->_libraryID) {
- throw new Exception("Library ID must be set before saving");
- }
-
- Zotero_Searches::editCheck($this, $userID);
-
- if (!$this->hasChanged()) {
- Z_Core::debug("Search $this->_id has not changed");
- return false;
- }
-
- if (!isset($this->_name) || $this->_name === '') {
- throw new Exception("Name not provided for saved search");
- }
-
- $shardID = Zotero_Shards::getByLibraryID($this->_libraryID);
-
- Zotero_DB::beginTransaction();
-
- $env = [];
- $isNew = $env['isNew'] = !$this->_id || !$this->exists();
-
- try {
- $searchID = $env['id'] = $this->_id ? $this->_id : Zotero_ID::get('savedSearches');
-
- Z_Core::debug("Saving search $this->_id");
- $key = $env['key'] = $this->_key ? $this->_key : Zotero_ID::getKey();
-
- $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,
- serverDateModified=?, version=?";
- $timestamp = Zotero_DB::getTransactionTimestamp();
- $params = array(
- $this->_name,
- $this->_libraryID,
- $key,
- $this->_dateAdded ? $this->_dateAdded : $timestamp,
- $this->_dateModified ? $this->_dateModified : $timestamp,
- $timestamp,
- Zotero_Libraries::getUpdatedVersion($this->_libraryID)
- );
- $shardID = Zotero_Shards::getByLibraryID($this->_libraryID);
-
- if ($isNew) {
- $sql = "INSERT INTO savedSearches SET searchID=?, $fields";
- $stmt = Zotero_DB::getStatement($sql, true, $shardID);
- Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params));
-
- // Remove from delete log if it's there
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?";
- Zotero_DB::query($sql, array($this->_libraryID, $key), $shardID);
- }
- else {
- $sql = "UPDATE savedSearches SET $fields WHERE searchID=?";
- $stmt = Zotero_DB::getStatement($sql, true, $shardID);
- Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID)));
- }
-
- if (!empty($this->changed['conditions'])) {
- if (!$isNew) {
- $sql = "DELETE FROM savedSearchConditions WHERE searchID=?";
- Zotero_DB::query($sql, $searchID, $shardID);
- }
-
- foreach ($this->conditions as $searchConditionID => $condition) {
- $sql = "INSERT INTO savedSearchConditions (searchID,
- searchConditionID, `condition`, mode, operator,
- value, required) VALUES (?,?,?,?,?,?,?)";
- $sqlParams = [
- $searchID,
- // Index search conditions from 1
- $searchConditionID + 1,
- $condition['condition'],
- $condition['mode'] ? $condition['mode'] : '',
- $condition['operator'] ? $condition['operator'] : '',
- $condition['value'] ? $condition['value'] : '',
- !empty($condition['required']) ? 1 : 0
- ];
- try {
- Zotero_DB::query($sql, $sqlParams, $shardID);
- }
- catch (Exception $e) {
- $msg = $e->getMessage();
- if (strpos($msg, "Data too long for column 'value'") !== false) {
- throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->_name . "'");
- }
- throw ($e);
- }
- }
- }
-
- Zotero_DB::commit();
- }
- catch (Exception $e) {
- Zotero_DB::rollback();
- throw ($e);
- }
-
- $this->finalizeSave($env);
-
- return $isNew ? $this->_id : true;
- }
-
-
- public function updateConditions($conditions) {
- $this->loadPrimaryData();
- $this->loadConditions();
-
- for ($i = 1, $len = sizeOf($conditions); $i <= $len; $i++) {
- // Compare existing values to new values
- if (isset($this->conditions[$i])) {
- if ($this->conditions[$i]['condition'] == $conditions[$i - 1]['condition']
- && $this->conditions[$i]['mode'] == $conditions[$i - 1]['mode']
- && $this->conditions[$i]['operator'] == $conditions[$i - 1]['operator']
- && $this->conditions[$i]['value'] == $conditions[$i - 1]['value']) {
- continue;
- }
- }
- $this->changed['conditions'] = true;
- }
- if (!empty($this->changed['conditions']) || sizeOf($this->conditions) > $conditions) {
- $this->conditions = $conditions;
- }
- else {
- Z_Core::debug("Conditions have not changed for search $this->id");
- }
- }
-
-
- /**
- * Returns an array with 'condition', 'mode', 'operator', and 'value'
- * for the given searchConditionID
- */
- public function getSearchCondition($searchConditionID) {
- $this->loadPrimaryData();
- $this->loadConditions();
-
- return isset($this->conditions[$searchConditionID])
- ? $this->conditions[$searchConditionID] : false;
- }
-
-
- /**
- * Returns a multidimensional array of conditions/mode/operator/value sets
- * used in the search, indexed by searchConditionID
- */
- public function getSearchConditions() {
- $this->loadPrimaryData();
- $this->loadConditions();
-
- return $this->conditions;
- }
-
-
- public function toResponseJSON($requestParams=[], Zotero_Permissions $permissions) {
- $t = microtime(true);
-
- $this->loadPrimaryData();
-
- $json = [
- 'key' => $this->key,
- 'version' => $this->version,
- 'library' => Zotero_Libraries::toJSON($this->libraryID)
- ];
-
- // 'links'
- $json['links'] = [
- 'self' => [
- 'href' => Zotero_API::getSearchURI($this),
- 'type' => 'application/json'
- ]/*,
- 'alternate' => [
- 'href' => Zotero_URI::getSearchURI($this, true),
- 'type' => 'text/html'
- ]*/
- ];
-
-
- // 'include'
- $include = $requestParams['include'];
- foreach ($include as $type) {
- if ($type == 'data') {
- $json[$type] = $this->toJSON($requestParams);
- }
- }
-
- return $json;
- }
-
-
- public function toJSON(array $requestParams=[]) {
- $this->loadPrimaryData();
- $this->loadConditions();
-
- if ($requestParams['v'] >= 3) {
- $arr['key'] = $this->key;
- $arr['version'] = $this->version;
- }
- else {
- $arr['searchKey'] = $this->key;
- $arr['searchVersion'] = $this->version;
- }
- $arr['name'] = $this->name;
- $arr['conditions'] = array();
-
- foreach ($this->conditions as $condition) {
- $arr['conditions'][] = array(
- 'condition' => $condition['condition']
- . ($condition['mode'] ? "/{$condition['mode']}" : ""),
- 'operator' => $condition['operator'],
- 'value' => $condition['value']
- );
- }
-
- return $arr;
- }
-
-
- /**
- * Generate a SimpleXMLElement Atom object for the search
- *
- * @param array $queryParams
- * @return SimpleXMLElement
- */
- public function toAtom($queryParams) {
- $this->loadPrimaryData();
- $this->loadConditions();
-
- // TEMP: multi-format support
- if (!empty($queryParams['content'])) {
- $content = $queryParams['content'];
- }
- else {
- $content = array('none');
- }
- $content = $content[0];
-
- $xml = new SimpleXMLElement(
- ''
- . ' '
- );
-
- $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;
- }
-
-
- protected function loadConditions($reload = false) {
- if (!$this->identified) return;
- if ($this->loaded['conditions'] && !$reload) return;
-
- $sql = "SELECT * FROM savedSearchConditions
- WHERE searchID=? ORDER BY searchConditionID";
- $conditions = Zotero_DB::query(
- $sql, $this->_id, Zotero_Shards::getByLibraryID($this->_libraryID)
- );
-
- $this->conditions = [];
- foreach ($conditions as $condition) {
- $searchConditionID = $condition['searchConditionID'];
- $this->conditions[$searchConditionID] = [
- 'id' => $searchConditionID,
- 'condition' => $condition['condition'],
- 'mode' => $condition['mode'],
- 'operator' => $condition['operator'],
- 'value' => $condition['value'],
- 'required' => $condition['required']
- ];
- }
-
- $this->loaded['conditions'] = true;
- $this->clearChanged('conditions');
- }
-}
-?>
diff --git a/model/Searches.inc.php b/model/Searches.inc.php
deleted file mode 100644
index 0d0c0fe1..00000000
--- a/model/Searches.inc.php
+++ /dev/null
@@ -1,365 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Searches {
- use Zotero_DataObjects;
-
- private static $objectType = 'search';
- private static $_table = 'savedSearches';
- private static $primaryDataSQLParts = [
- 'id' => 'O.searchID',
- 'name' => 'O.searchName',
- 'libraryID' => 'O.libraryID',
- 'key' => 'O.key',
- 'dateAdded' => 'O.dateAdded',
- 'dateModified' => 'O.dateModified',
- 'version' => 'O.version'
- ];
-
-
- public static function search($libraryID, $params) {
- $results = array('results' => array(), 'total' => 0);
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT ";
- if ($params['format'] == 'keys') {
- $sql .= "`key`";
- }
- else if ($params['format'] == 'versions') {
- $sql .= "`key`, version";
- }
- else {
- $sql .= "searchID";
- }
- $sql .= " FROM savedSearches WHERE libraryID=? ";
- $sqlParams = array($libraryID);
-
- // Pass a list of searchIDs, for when the initial search is done via SQL
- $searchIDs = !empty($params['searchIDs'])
- ? $params['searchIDs'] : array();
- // Or keys, for the searchKey parameter
- $searchKeys = $params['searchKey'];
-
- if (!empty($params['since'])) {
- $sql .= "AND version > ? ";
- $sqlParams[] = $params['since'];
- }
-
- // TEMP: for sync transition
- if (!empty($params['sincetime'])) {
- $sql .= "AND serverDateModified >= FROM_UNIXTIME(?) ";
- $sqlParams[] = $params['sincetime'];
- }
-
- if ($searchIDs) {
- $sql .= "AND searchID IN ("
- . implode(', ', array_fill(0, sizeOf($searchIDs), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $searchIDs);
- }
-
- if ($searchKeys) {
- $sql .= "AND `key` IN ("
- . implode(', ', array_fill(0, sizeOf($searchKeys), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $searchKeys);
- }
-
- if (!empty($params['sort'])) {
- switch ($params['sort']) {
- case 'title':
- $orderSQL = 'searchName';
- break;
-
- case 'searchKeyList':
- $orderSQL = "FIELD(`key`,"
- . implode(',', array_fill(0, sizeOf($searchKeys), '?')) . ")";
- $sqlParams = array_merge($sqlParams, $searchKeys);
- break;
-
- default:
- $orderSQL = $params['sort'];
- }
-
- $sql .= "ORDER BY $orderSQL";
- if (!empty($params['direction'])) {
- $sql .= " {$params['direction']}";
- }
- $sql .= ", ";
- }
- $sql .= "version " . (!empty($params['direction']) ? $params['direction'] : "ASC")
- . ", searchID " . (!empty($params['direction']) ? $params['direction'] : "ASC") . " ";
-
- if (!empty($params['limit'])) {
- $sql .= "LIMIT ?, ?";
- $sqlParams[] = $params['start'] ? $params['start'] : 0;
- $sqlParams[] = $params['limit'];
- }
-
- if ($params['format'] == 'versions') {
- $rows = Zotero_DB::query($sql, $sqlParams, $shardID);
- }
- // keys and ids
- else {
- $rows = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
- }
-
- $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
- if ($rows) {
- if ($params['format'] == 'keys') {
- $results['results'] = $rows;
- }
- else if ($params['format'] == 'versions') {
- foreach ($rows as $row) {
- $results['results'][$row['key']] = $row['version'];
- }
- }
- else {
- $searches = array();
- foreach ($rows as $id) {
- $searches[] = self::get($libraryID, $id);
- }
- $results['results'] = $searches;
- }
- }
-
- return $results;
- }
-
-
- /**
- * Converts a SimpleXMLElement item to a Zotero_Search object
- *
- * @param SimpleXMLElement $xml Search data as SimpleXML element
- * @return Zotero_Search Zotero search object
- */
- public static function convertXMLToSearch(SimpleXMLElement $xml) {
- $search = new Zotero_Search;
- $search->libraryID = (int) $xml['libraryID'];
- $search->key = (string) $xml['key'];
- $search->name = (string) $xml['name'];
- $search->dateAdded = (string) $xml['dateAdded'];
- $search->dateModified = (string) $xml['dateModified'];
-
- $conditions = array();
- foreach($xml->condition as $condition) {
- $conditions[] = array(
- 'condition' => (string) $condition['condition'],
- 'mode' => (string) $condition['mode'],
- 'operator' => (string) $condition['operator'],
- 'value' => (string) $condition['value']
- );
- }
- $search->updateConditions($conditions);
-
- return $search;
- }
-
-
- /**
- * Converts a Zotero_Search object to a SimpleXMLElement item
- *
- * @param object $item Zotero_Search object
- * @return SimpleXMLElement Search data as SimpleXML element
- */
- public static function convertSearchToXML(Zotero_Search $search) {
- $xml = new SimpleXMLElement(' ');
- $xml['libraryID'] = $search->libraryID;
- $xml['key'] = $search->key;
- $xml['name'] = $search->name;
- $xml['dateAdded'] = $search->dateAdded;
- $xml['dateModified'] = $search->dateModified;
-
- $conditions = $search->getSearchConditions();
-
- if ($conditions) {
- foreach($conditions as $condition) {
- $c = $xml->addChild('condition');
- $c['id'] = $condition['id'];
- $c['condition'] = $condition['condition'];
- if ($condition['mode']) {
- $c['mode'] = $condition['mode'];
- }
- $c['operator'] = $condition['operator'];
- $c['value'] = $condition['value'];
- if ($condition['required']) {
- $c['required'] = "1";
- }
- }
- }
-
- return $xml;
- }
-
-
- /**
- * @param Zotero_Searches $search The search object to update;
- * this should be either an existing
- * search or a new search
- * with a library assigned.
- * @param object $json Search data to write
- * @param boolean $requireVersion See Zotero_API::checkJSONObjectVersion()
- * @return bool True if the search was changed, false otherwise
- */
- public static function updateFromJSON(Zotero_Search $search,
- $json,
- $requestParams,
- $userID,
- $requireVersion=0,
- $partialUpdate=false) {
- $json = Zotero_API::extractEditableJSON($json);
- $exists = Zotero_API::processJSONObjectKey($search, $json, $requestParams);
- Zotero_API::checkJSONObjectVersion($search, $json, $requestParams, $requireVersion);
- self::validateJSONSearch($json, $requestParams, $partialUpdate && $exists);
-
- if (isset($json->name)) {
- $search->name = $json->name;
- }
-
- if (isset($json->conditions)) {
- $conditions = [];
- foreach ($json->conditions as $condition) {
- $newCondition = get_object_vars($condition);
- // Parse 'mode' (e.g., '/regexp') out of condition name
- if (preg_match('/(.+)\/(.+)/', $newCondition['condition'], $matches)) {
- $newCondition['condition'] = $matches[1];
- $newCondition['mode'] = $matches[2];
- }
- else {
- $newCondition['mode'] = "";
- }
- $conditions[] = $newCondition;
- }
- $search->updateConditions($conditions);
- }
- return !!$search->save();
- }
-
-
- private static function validateJSONSearch($json, $requestParams, $partialUpdate=false) {
- if (!is_object($json)) {
- throw new Exception('$json must be a decoded JSON object');
- }
-
- if ($partialUpdate) {
- $requiredProps = [];
- }
- else {
- $requiredProps = ['name', 'conditions'];
- }
- foreach ($requiredProps as $prop) {
- if (!isset($json->$prop)) {
- throw new Exception("'$prop' property not provided", Z_ERROR_INVALID_INPUT);
- }
- }
- foreach ($json as $key => $val) {
- switch ($key) {
- // Handled by Zotero_API::checkJSONObjectVersion()
- case 'key':
- case 'version':
- case 'searchKey':
- case 'searchVersion':
- break;
-
- case 'name':
- if (!is_string($val)) {
- throw new Exception("'name' must be a string", Z_ERROR_INVALID_INPUT);
- }
-
- if ($val === "") {
- throw new Exception("Search name cannot be empty", Z_ERROR_INVALID_INPUT);
- }
-
- if (mb_strlen($val) > 255) {
- throw new Exception("Search name cannot be longer than 255 characters", Z_ERROR_FIELD_TOO_LONG);
- }
- break;
-
- case 'conditions':
- if (!is_array($val)) {
- throw new Exception("'conditions' must be an array (" . gettype($val) . ")", Z_ERROR_INVALID_INPUT);
- }
- if (empty($val)) {
- throw new Exception("'conditions' cannot be empty", Z_ERROR_INVALID_INPUT);
- }
-
- foreach ($val as $condition) {
- $requiredProps = ['condition', 'operator', 'value'];
- foreach ($requiredProps as $prop) {
- if (!isset($condition->$prop)) {
- throw new Exception("'$prop' property not provided for search condition", Z_ERROR_INVALID_INPUT);
- }
- }
-
- foreach ($condition as $conditionKey => $conditionVal) {
- if (!is_string($conditionVal)) {
- throw new Exception("'$conditionKey' must be a string", Z_ERROR_INVALID_INPUT);
- }
-
- switch ($conditionKey) {
- case 'condition':
- if ($conditionVal === "") {
- throw new Exception("Search condition cannot be empty", Z_ERROR_INVALID_INPUT);
- }
- $maxLen = 50;
- if (strlen($conditionVal) > $maxLen) {
- throw new Exception("Search condition cannot be longer than $maxLen characters", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- case 'operator':
- if ($conditionVal === "") {
- throw new Exception("Search operator cannot be empty", Z_ERROR_INVALID_INPUT);
- }
- $maxLen = 25;
- if (strlen($conditionVal) > $maxLen) {
- throw new Exception("Search operator cannot be longer than $maxLen characters", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- case 'value':
- $maxLen = 255;
- if (strlen($conditionVal) > $maxLen) {
- throw new Exception("Search operator cannot be longer than $maxLen characters", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- default:
- throw new Exception("Invalid property '$conditionKey' for search condition", Z_ERROR_INVALID_INPUT);
- }
- }
- }
- break;
-
- default:
- throw new Exception("Invalid property '$key'", Z_ERROR_INVALID_INPUT);
- }
- }
- }
-}
-
-Zotero_Searches::init();
diff --git a/model/Setting.inc.php b/model/Setting.inc.php
deleted file mode 100644
index df68a98c..00000000
--- a/model/Setting.inc.php
+++ /dev/null
@@ -1,237 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Setting {
- private $libraryID;
- private $name;
- private $value;
- private $version = 0;
-
- private $loaded;
- private $changed;
-
-
- public function __get($prop) {
- if ($this->name && !$this->loaded) {
- $this->load();
- }
-
- if (!property_exists('Zotero_Setting', $prop)) {
- throw new Exception("Zotero_Setting property '$prop' doesn't exist");
- }
-
- return $this->$prop;
- }
-
-
- public function __set($prop, $value) {
- switch ($prop) {
- case 'version':
- throw new Exception("Cannot modify version");
-
- case 'libraryID':
- case 'name':
- if ($this->loaded) {
- throw new Exception("Cannot set $prop after setting is already loaded");
- }
- $this->checkProperty($prop, $value);
- $this->$prop = $value;
- return;
- }
-
- if ($this->name) {
- if (!$this->loaded) {
- $this->load();
- }
- }
- else {
- $this->loaded = true;
- }
-
- $this->checkProperty($prop, $value);
-
- if ($this->$prop != $value) {
- //Z_Core::debug("Setting property '$prop' has changed from '{$this->$prop}' to '$value'");
- $this->changed = true;
- $this->$prop = $value;
- }
- }
-
-
- /**
- * Check if setting exists in the database
- *
- * @return bool TRUE if the setting exists, FALSE if not
- */
- public function exists() {
- $sql = "SELECT COUNT(*) FROM settings WHERE libraryID=? AND name=?";
- return !!Zotero_DB::valueQuery(
- $sql,
- array($this->libraryID, $this->name),
- Zotero_Shards::getByLibraryID($this->libraryID)
- );
- }
-
-
- /**
- * Save the setting to the DB
- */
- public function save($userID=false) {
- if (!$this->libraryID) {
- throw new Exception("libraryID not set");
- }
- if (!isset($this->name) || $this->name === '') {
- throw new Exception("Setting name not provided");
- }
-
- try {
- Zotero_Settings::editCheck($this, $userID);
- }
- // TEMP: Ignore this for now, since there seems to be a client bug as of 4.0.17 that can
- // cause settings from deleted libraries to remain
- catch (Exception $e) {
- error_log("WARNING: " . $e);
- return false;
- }
-
- if (!$this->changed) {
- Z_Core::debug("Setting $this->libraryID/$this->name has not changed");
- return false;
- }
-
- $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
-
- Zotero_DB::beginTransaction();
-
- $isNew = !$this->exists();
-
- try {
- Z_Core::debug("Saving setting $this->libraryID/$this->name");
-
- $params = array(
- json_encode($this->value),
- Zotero_Libraries::getUpdatedVersion($this->libraryID),
- Zotero_DB::getTransactionTimestamp()
- );
- $params = array_merge(array($this->libraryID, $this->name), $params, $params);
-
- $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
-
- $sql = "INSERT INTO settings (libraryID, name, value, version, lastUpdated) "
- . "VALUES (?, ?, ?, ?, ?) "
- . "ON DUPLICATE KEY UPDATE value=?, version=?, lastUpdated=?";
- Zotero_DB::query($sql, $params, $shardID);
-
- // Remove from delete log if it's there
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='setting' AND `key`=?";
- Zotero_DB::query($sql, array($this->libraryID, $this->name), $shardID);
-
- Zotero_DB::commit();
- }
- catch (Exception $e) {
- Zotero_DB::rollback();
- throw ($e);
- }
-
- return true;
- }
-
-
- public function toJSON($asArray=false, $requestParams=array()) {
- if (!$this->loaded) {
- $this->load();
- }
-
- $arr = array(
- 'value' => $this->value,
- 'version' => $this->version
- );
-
- if ($asArray) {
- return $arr;
- }
-
- return Zotero_Utilities::formatJSON($arr);
- }
-
-
- private function load() {
- $libraryID = $this->libraryID;
- $name = $this->name;
-
- Z_Core::debug("Loading data for setting $libraryID/$name");
-
- if (!$libraryID) {
- throw new Exception("Library ID not set");
- }
-
- if (!$name) {
- throw new Exception("Name not set");
- }
-
- $row = Zotero_Settings::getPrimaryDataByKey($libraryID, $name);
-
- $this->loaded = true;
-
- if (!$row) {
- return;
- }
-
- foreach ($row as $key => $val) {
- if ($key == 'value') {
- $val = json_decode($val);
- }
- $this->$key = $val;
- }
- }
-
-
- private function checkProperty($prop, $val) {
- if (!property_exists($this, $prop)) {
- throw new Exception("Invalid property '$prop'");
- }
-
- // Data validation
- switch ($prop) {
- case 'libraryID':
- if (!Zotero_Utilities::isPosInt($val)) {
- throw new Exception("Invalid '$prop' value '$value'");
- }
- break;
-
- case 'name':
- if (!in_array($val, Zotero_Settings::$allowedSettings)) {
- throw new Exception("Invalid setting '$val'", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- case 'value':
- Zotero_Settings::checkSettingValue($this->name, $val);
- break;
- }
- }
-}
-?>
diff --git a/model/Settings.inc.php b/model/Settings.inc.php
deleted file mode 100644
index a683f32e..00000000
--- a/model/Settings.inc.php
+++ /dev/null
@@ -1,274 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Settings extends Zotero_ClassicDataObjects {
- public static $MAX_VALUE_LENGTH = 20000;
-
- public static $allowedSettings = ['feeds', 'tagColors'];
-
- protected static $ZDO_object = 'setting';
- protected static $ZDO_key = 'name';
- protected static $ZDO_id = 'name';
- protected static $ZDO_timestamp = 'lastUpdated';
-
- protected static $primaryFields = array(
- 'libraryID' => '',
- 'name' => '',
- 'value' => '',
- 'version' => ''
- );
-
- public static function search($libraryID, $params) {
- // Default empty library
- if ($libraryID === 0) {
- return [];
- }
-
- $sql = "SELECT name FROM settings WHERE libraryID=?";
- $sqlParams = [$libraryID];
-
- if (!empty($params['since'])) {
- $sql .= " AND version > ? ";
- $sqlParams[] = $params['since'];
- }
-
- // TEMP: for sync transition
- if (!empty($params['sincetime'])) {
- $sql .= " AND lastUpdated >= FROM_UNIXTIME(?) ";
- $sqlParams[] = $params['sincetime'];
- }
-
- $names = Zotero_DB::columnQuery($sql, $sqlParams, Zotero_Shards::getByLibraryID($libraryID));
- if (!$names) {
- $names = array();
- }
-
- $settings = array();
- foreach ($names as $name) {
- $setting = new Zotero_Setting;
- $setting->libraryID = $libraryID;
- $setting->name = $name;
- $settings[] = $setting;
- }
- return $settings;
- }
-
-
-
- /**
- * Converts a DOMElement item to a Zotero_Setting object
- *
- * @param DOMElement $xml Setting data as DOMElement
- * @return Zotero_Setting Setting object
- */
- public static function convertXMLToSetting(DOMElement $xml) {
- $libraryID = (int) $xml->getAttribute('libraryID');
- $name = (string) $xml->getAttribute('name');
- $setting = self::getByLibraryAndKey($libraryID, $name);
- if (!$setting) {
- $setting = new Zotero_Setting;
- $setting->libraryID = $libraryID;
- $setting->name = $name;
- }
- $setting->value = json_decode((string) $xml->nodeValue);
- return $setting;
- }
-
-
- /**
- * Converts a Zotero_Setting object to a SimpleXMLElement item
- *
- * @param Zotero_Setting $item Zotero_Setting object
- * @return DOMElement
- */
- public static function convertSettingToXML(Zotero_Setting $setting, DOMDocument $doc) {
- $xmlSetting = $doc->createElement('setting');
- $xmlSetting->setAttribute('libraryID', $setting->libraryID);
- $xmlSetting->setAttribute('name', $setting->name);
- $xmlSetting->setAttribute('version', $setting->version);
- $xmlSetting->appendChild($doc->createTextNode(json_encode($setting->value)));
- return $xmlSetting;
- }
-
-
- /**
- * @param Zotero_Setting $setting The setting object to update;
- * this should be either an existing
- * setting or a new setting
- * with a library and name assigned.
- * @param object $json Setting data to write
- * @param boolean [$requireVersion=0] See Zotero_API::checkJSONObjectVersion()
- * @return boolean True if the setting was changed, false otherwise
- */
- public static function updateFromJSON(Zotero_Setting $setting,
- $json,
- $requestParams,
- $userID,
- $requireVersion=0) {
- self::validateJSONObject($setting->name, $json, $requestParams);
- Zotero_API::checkJSONObjectVersion(
- $setting, $json, $requestParams, $requireVersion
- );
-
- $changed = false;
-
- if (!Zotero_DB::transactionInProgress()) {
- Zotero_DB::beginTransaction();
- $transactionStarted = true;
- }
- else {
- $transactionStarted = false;
- }
-
- $setting->value = $json->value;
- $changed = $setting->save() || $changed;
-
- if ($transactionStarted) {
- Zotero_DB::commit();
- }
-
- return $changed;
- }
-
-
- private static function validateJSONObject($name, $json, $requestParams) {
- if (!is_object($json)) {
- throw new Exception('$json must be a decoded JSON object');
- }
-
- $requiredProps = array('value');
-
- if (!in_array($name, self::$allowedSettings)) {
- throw new Exception("Invalid setting '$name'", Z_ERROR_INVALID_INPUT);
- }
-
- foreach ($requiredProps as $prop) {
- if (!isset($json->$prop)) {
- throw new Exception("'$prop' property not provided", Z_ERROR_INVALID_INPUT);
- }
- }
-
- foreach ($json as $key=>$val) {
- switch ($key) {
- // Handled by Zotero_API::checkJSONObjectVersion()
- case 'version':
- break;
-
- case 'value':
- self::checkSettingValue($name, $val);
- break;
-
- default:
- throw new Exception("Invalid property '$key'", Z_ERROR_INVALID_INPUT);
- }
- }
- }
-
-
- public static function updateMultipleFromJSON($json, $requestParams, $libraryID, $userID, $requireVersion, $parent=null) {
- self::validateMultiObjectJSON($json, $requestParams);
-
- Zotero_DB::beginTransaction();
-
- $changed = false;
- foreach ($json as $name => $jsonObject) {
- if (!is_object($jsonObject)) {
- throw new Exception(
- "Invalid property '$name'; expected JSON setting object",
- Z_ERROR_INVALID_INPUT
- );
- }
-
- $obj = new Zotero_Setting;
- $obj->libraryID = $libraryID;
- $obj->name = $name;
- $changed = static::updateFromJSON(
- $obj, $jsonObject, $requestParams, $requireVersion
- ) || $changed;
- }
-
- Zotero_DB::commit();
-
- return $changed;
- }
-
-
- public static function checkSettingValue($setting, $value) {
- if (mb_strlen(is_string($value) ? $value : json_encode($value)) > self::$MAX_VALUE_LENGTH) {
- throw new Exception("'value' cannot be longer than "
- . self::$MAX_VALUE_LENGTH . " characters", Z_ERROR_INVALID_INPUT);
- }
-
- switch ($setting) {
- // Object settings
- case 'feeds':
- if (!is_object($value)) {
- throw new Exception("'value' must be an object", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- // Array settings
- case 'tagColors':
- if (!is_array($value)) {
- throw new Exception("'value' must be an array", Z_ERROR_INVALID_INPUT);
- }
-
- if (empty($value)) {
- throw new Exception("'value' array cannot be empty", Z_ERROR_INVALID_INPUT);
- }
- break;
-
- // String settings
- default:
- if (!is_string($value)) {
- throw new Exception("'value' be a string", Z_ERROR_INVALID_INPUT);
- }
-
- if ($val === "") {
- throw new Exception("'value' cannot be empty", Z_ERROR_INVALID_INPUT);
- }
- break;
- }
- }
-
-
- protected static function validateMultiObjectJSON($json, $requestParams) {
- if (!is_object($json)) {
- throw new Exception('$json must be a decoded JSON object');
- }
-
- if (sizeOf(get_object_vars($json)) > Zotero_API::$maxWriteSettings) {
- throw new Exception("Cannot add more than "
- . Zotero_API::$maxWriteSettings
- . " settings at a time", Z_ERROR_UPLOAD_TOO_LARGE);
- }
- }
-
-
- private static function invalidValueError($prop, $value) {
- throw new Exception("Invalid '$prop' value '$value'");
- }
-}
diff --git a/model/StorageFileInfo.inc.php b/model/StorageFileInfo.inc.php
deleted file mode 100644
index 9d515a23..00000000
--- a/model/StorageFileInfo.inc.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
-class Zotero_StorageFileInfo {
- public $hash;
- public $filename;
- public $mtime;
- public $size;
- public $contentType;
- public $charset;
- public $zip = false;
- public $itemHash;
- public $itemFilename;
-
- public function toJSON() {
- return json_encode(get_object_vars($this));
- }
-}
diff --git a/model/Sync.inc.php b/model/Sync.inc.php
deleted file mode 100644
index 91a9dc45..00000000
--- a/model/Sync.inc.php
+++ /dev/null
@@ -1,2288 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Sync {
- public static $defaultAPIVersion = 8;
-
- public static $validationError = '';
-
- // Don't bother error-checking uploads below this size
- private static $minErrorCheckSize = 5000;
- // Don't process uploads larger than this that haven't been error-checked
- private static $minErrorCheckRequiredSize = 30000;
- // Don't process uploads larger than this in smallestFirst mode
- private static $maxSmallestSize = 200000;
-
- // This needs to be incremented any time there's a change to the sync response
- private static $cacheVersion = 1;
-
- private static $queueDataThreshold = 65535;
-
- public static function getResponseXML($version=null) {
- if (!$version) {
- $version = self::$defaultAPIVersion;
- }
-
- $xml = new SimpleXMLElement(' ');
- $xml['version'] = $version;
-
- // Generate a fixed timestamp for the response
- //
- // Responses that modify data need to override this with the DB transaction
- // timestamp if the client will use the returned timestamp for comparison purposes
- $xml['timestamp'] = time();
- return $xml;
- }
-
-
- /**
- * Check if any of a user's libraries are queued for writing
- *
- * Clients can still read (/updated) but cannot write (/upload) if this is true
- */
- public static function userIsReadLocked($userID) {
- Zotero_DB::beginTransaction();
-
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
- $sql = "SELECT COUNT(*) FROM syncUploadQueueLocks WHERE libraryID IN (";
- $sql .= implode(', ', array_fill(0, sizeOf($libraryIDs), '?'));
- $sql .= ")";
-
- $locked = !!Zotero_DB::valueQuery($sql, $libraryIDs);
-
- Zotero_DB::commit();
-
- return $locked;
- }
-
-
- /**
- * Check if any of a user's libraries are being written to
- *
- * Clients can't read (/updated) or write (/upload) if this is true
- */
- public static function userIsWriteLocked($userID) {
- Zotero_DB::beginTransaction();
-
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
-
- $sql = "SELECT COUNT(*) FROM syncProcessLocks WHERE libraryID IN (";
- $sql .= implode(', ', array_fill(0, sizeOf($libraryIDs), '?'));
- $sql .= ")";
-
- $locked = !!Zotero_DB::valueQuery($sql, $libraryIDs);
-
- Zotero_DB::commit();
-
- return $locked;
- }
-
-
- /**
- * Get all the libraryIDs referenced in upload XML
- */
- public static function parseAffectedLibraries($xmlstr) {
- preg_match_all('/<[^>]+ libraryID="([0-9]+)"/', $xmlstr, $matches);
- $unique = array_values(array_unique($matches[1]));
- array_walk($unique, function (&$a) {
- $a = (int) $a;
- });
- return $unique;
- }
-
-
- public static function queueDownload($userID, $sessionID, $lastsync, $version, $updatedObjects, $params=array()) {
- $syncQueueID = Zotero_ID::getBigInt();
-
- // If there's a completed process from this session, delete it, since it
- // seems the results aren't going to be picked up
- $sql = "DELETE FROM syncDownloadQueue WHERE sessionID=? AND finished IS NOT NULL";
- Zotero_DB::query($sql, $sessionID);
-
- $sql = "INSERT INTO syncDownloadQueue
- (syncDownloadQueueID, processorHost, userID, sessionID, lastsync, version, params, objects)
- VALUES (?, INET_ATON(?), ?, ?, FROM_UNIXTIME(?), ?, ?, ?)";
- Zotero_DB::query(
- $sql,
- array(
- $syncQueueID,
- gethostbyname(gethostname()),
- $userID,
- $sessionID,
- $lastsync,
- $version,
- json_encode($params),
- $updatedObjects
- )
- );
-
- return $syncQueueID;
- }
-
-
- public static function queueUpload($userID, $sessionID, $xmldata, $affectedLibraries) {
- $syncQueueID = Zotero_ID::getBigInt();
- $length = strlen($xmldata);
-
- // If there's a completed process from this session, delete it, since it
- // seems the results aren't going to be picked up
- $sql = "DELETE FROM syncUploadQueue WHERE sessionID=? AND finished IS NOT NULL";
- Zotero_DB::query($sql, $sessionID);
-
- // Strip control characters in XML data
- $xmldata = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $xmldata);
-
- // If too big for DB, store in S3 and store pointer instead
- $xmldata = self::storeQueueData('upload', $xmldata);
-
- Zotero_DB::beginTransaction();
-
- $sql = "INSERT INTO syncUploadQueue
- (syncUploadQueueID, processorHost, userID, sessionID, xmldata, dataLength, hasCreator)
- VALUES (?, INET_ATON(?), ?, ?, ?, ?, ?)";
- Zotero_DB::query(
- $sql,
- array(
- $syncQueueID,
- gethostbyname(gethostname()),
- $userID,
- $sessionID,
- $xmldata,
- $length,
- strpos($xmldata, 'getMessage();
- if (strpos($msg, "Cannot add or update a child row: a foreign key constraint fails") !== false) {
- foreach ($affectedLibraries as $libraryID) {
- if (!Zotero_DB::valueQuery("SELECT COUNT(*) FROM libraries WHERE libraryID=?", $libraryID)) {
- throw new Exception("Library $libraryID does not exist", Z_ERROR_LIBRARY_ACCESS_DENIED);
- }
- }
- }
- throw ($e);
- }
-
- Zotero_DB::commit();
-
- return $syncQueueID;
- }
-
-
- public static function processDownload($userID, $lastsync, DOMDocument $doc, $params=[]) {
- self::processDownloadInternal($userID, $lastsync, $doc, null, null, $params);
- }
-
-
- public static function processUpload($userID, SimpleXMLElement $xml) {
- return self::processUploadInternal($userID, $xml);
- }
-
-
- public static function processDownloadFromQueue($syncProcessID) {
- Zotero_DB::beginTransaction();
-
- // Get a queued process
- $smallestFirst = Z_CONFIG::$SYNC_DOWNLOAD_SMALLEST_FIRST;
- $sql = "SELECT syncDownloadQueueID, SDQ.userID,
- UNIX_TIMESTAMP(lastsync) AS lastsync, version, params, added, objects, ipAddress
- FROM syncDownloadQueue SDQ JOIN sessions USING (sessionID)
- WHERE started IS NULL ORDER BY tries > 4, ";
- if ($smallestFirst) {
- $sql .= "ROUND(objects / 100), ";
- }
- $sql .= "added LIMIT 1 FOR UPDATE";
- $row = Zotero_DB::rowQuery($sql);
-
- // No pending processes
- if (!$row) {
- Zotero_DB::commit();
- return 0;
- }
-
- $host = gethostbyname(gethostname());
- $startedTimestamp = microtime(true);
-
- $sql = "UPDATE syncDownloadQueue SET started=FROM_UNIXTIME(?), processorHost=INET_ATON(?) WHERE syncDownloadQueueID=?";
- Zotero_DB::query($sql, array(round($startedTimestamp), $host, $row['syncDownloadQueueID']));
-
- Zotero_DB::commit();
-
- $error = false;
- $lockError = false;
-
- try {
- if (Zotero_Sync::userIsWriteLocked($row['userID'])) {
- $lockError = true;
- throw new Exception("User is write locked");
- }
-
- $xml = self::getResponseXML($row['version']);
- $doc = new DOMDocument();
- $domResponse = dom_import_simplexml($xml);
- $domResponse = $doc->importNode($domResponse, true);
- $doc->appendChild($domResponse);
-
- $params = !empty($row['params']) ? json_decode($row['params'], true) : [];
-
- self::processDownloadInternal($row['userID'], $row['lastsync'], $doc, $row['syncDownloadQueueID'], $syncProcessID, $params);
- }
- catch (Exception $e) {
- $error = true;
- $code = $e->getCode();
- $msg = $e->getMessage();
- }
-
- Zotero_DB::beginTransaction();
-
- // Mark download as finished — NULL indicates success
- if (!$error) {
- $timestamp = $doc->documentElement->getAttribute('timestamp');
-
- $xmldata = $doc->saveXML();
- $size = strlen($xmldata);
-
- // If too big for DB, store in S3 and store pointer instead
- $xmldata = self::storeQueueData('download', $xmldata);
-
- $sql = "UPDATE syncDownloadQueue SET finished=FROM_UNIXTIME(?), xmldata=? WHERE syncDownloadQueueID=?";
- Zotero_DB::query(
- $sql,
- array(
- $timestamp,
- $xmldata,
- $row['syncDownloadQueueID']
- )
- );
-
- StatsD::increment("sync.process.download.queued.success");
- StatsD::updateStats("sync.process.download.queued.size", $size);
- StatsD::timing("sync.process.download.process", round((microtime(true) - $startedTimestamp) * 1000));
- StatsD::timing("sync.process.download.total", max(0, time() - strtotime($row['added'])) * 1000);
-
- self::logDownload(
- $row['userID'],
- round($row['lastsync']),
- $row['objects'],
- $row['ipAddress'],
- $host,
- round((float) microtime(true) - $startedTimestamp, 2),
- max(0, min(time() - strtotime($row['added']), 65535)),
- 0
- );
- }
- // Timeout/connection error
- else if (
- $lockError
- || strpos($msg, "Lock wait timeout exceeded; try restarting transaction") !== false
- || strpos($msg, "Deadlock found when trying to get lock; try restarting transaction") !== false
- || strpos($msg, "Too many connections") !== false
- || strpos($msg, "Can't connect to MySQL server") !==false
- || $code == Z_ERROR_SHARD_UNAVAILABLE
- ) {
- Z_Core::logError($e);
- $sql = "UPDATE syncDownloadQueue SET started=NULL, tries=tries+1 WHERE syncDownloadQueueID=?";
- Zotero_DB::query($sql, $row['syncDownloadQueueID']);
- $lockError = true;
- StatsD::increment("sync.process.download.queued.errorTemporary");
- }
- // Save error
- else {
- Z_Core::logError($e);
- $sql = "UPDATE syncDownloadQueue SET finished=?, errorCode=?,
- errorMessage=? WHERE syncDownloadQueueID=?";
- Zotero_DB::query(
- $sql,
- array(
- Zotero_DB::getTransactionTimestamp(),
- $e->getCode(),
- substr(serialize($e), 0, 65535),
- $row['syncDownloadQueueID']
- )
- );
-
- StatsD::increment("sync.process.download.queued.errorPermanent");
-
- self::logDownload(
- $row['userID'],
- $row['lastsync'],
- $row['objects'],
- $row['ipAddress'],
- $host,
- round((float) microtime(true) - $startedTimestamp, 2),
- max(0, min(time() - strtotime($row['added']), 65535)),
- 1
- );
- }
-
- Zotero_DB::commit();
-
- if ($lockError) {
- return -1;
- }
- else if ($error) {
- return -2;
- }
-
- return 1;
- }
-
-
- public static function processUploadFromQueue($syncProcessID) {
- if (Z_Core::probability(30)) {
- $sql = "DELETE FROM syncProcesses WHERE started < (NOW() - INTERVAL 180 MINUTE)";
- Zotero_DB::query($sql);
- }
-
- if (Z_Core::probability(30)) {
- $sql = "UPDATE syncUploadQueue SET started=NULL WHERE started IS NOT NULL AND errorCheck!=1 AND
- started < (NOW() - INTERVAL 12 MINUTE) AND finished IS NULL AND dataLength<250000";
- Zotero_DB::query($sql);
- }
-
- if (Z_Core::probability(30)) {
- $sql = "UPDATE syncUploadQueue SET tries=0 WHERE started IS NULL AND
- tries>=5 AND finished IS NULL";
- Zotero_DB::query($sql);
- }
-
- Zotero_DB::beginTransaction();
-
- // Get a queued process
- $smallestFirst = Z_CONFIG::$SYNC_UPLOAD_SMALLEST_FIRST;
- $sortByQuota = !empty(Z_CONFIG::$SYNC_UPLOAD_SORT_BY_QUOTA);
-
- $sql = "SELECT syncUploadQueue.* FROM syncUploadQueue ";
- if ($sortByQuota) {
- $sql .= "LEFT JOIN storageAccounts USING (userID) ";
- }
- $sql .= "WHERE started IS NULL ";
- if (self::$minErrorCheckRequiredSize) {
- $sql .= "AND (errorCheck=2 OR dataLength<" . self::$minErrorCheckRequiredSize . ") ";
- }
- if ($smallestFirst && self::$maxSmallestSize) {
- $sql .= "AND dataLength<" . self::$maxSmallestSize . " ";
- }
- $sql .= "ORDER BY tries > 4, ";
- if ($sortByQuota) {
- $sql .= "quota DESC, ";
- }
- if ($smallestFirst) {
- //$sql .= "ROUND(dataLength / 1024 / 10), ";
- $sql .= "dataLength, ";
- }
- $sql .= "added LIMIT 1 FOR UPDATE";
- $row = Zotero_DB::rowQuery($sql);
-
- // No pending processes
- if (!$row) {
- Zotero_DB::commit();
- return 0;
- }
-
- $host = gethostbyname(gethostname());
-
- $startedTimestamp = microtime(true);
- list($started, $startedMS) = self::getTimestampParts($startedTimestamp);
- $sql = "UPDATE syncUploadQueue SET started=FROM_UNIXTIME(?), processorHost=INET_ATON(?) WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, array($started, $host, $row['syncUploadQueueID']));
-
- Zotero_DB::commit();
- Zotero_DB::close();
-
- $processData = array(
- "syncUploadQueueID" => $row['syncUploadQueueID'],
- "userID" => $row['userID'],
- "dataLength" => $row['dataLength']
- );
- Z_Core::$MC->set("syncUploadProcess_" . $syncProcessID, $processData, 86400);
-
- $error = false;
- $lockError = false;
- try {
- $queueDataID = self::getQueueDataIDFromField($row['xmldata']);
- if ($queueDataID) {
- $row['xmldata'] = self::getQueueData('upload', $queueDataID);
- }
- $xml = new SimpleXMLElement($row['xmldata'], LIBXML_COMPACT | LIBXML_PARSEHUGE);
- $timestamp = self::processUploadInternal($row['userID'], $xml, $row['syncUploadQueueID'], $syncProcessID);
- }
- catch (Exception $e) {
- $error = true;
- $code = $e->getCode();
- $msg = $e->getMessage();
- }
-
- Zotero_DB::beginTransaction();
-
- // Mark upload as finished — NULL indicates success
- if (!$error) {
- $sql = "UPDATE syncUploadQueue SET finished=FROM_UNIXTIME(?) WHERE syncUploadQueueID=?";
- Zotero_DB::query(
- $sql,
- array(
- $timestamp,
- $row['syncUploadQueueID']
- )
- );
-
- StatsD::increment("sync.process.upload.success");
- StatsD::updateStats("sync.process.upload.size", $row['dataLength']);
- StatsD::timing("sync.process.upload.process", round((microtime(true) - $startedTimestamp) * 1000));
- StatsD::timing("sync.process.upload.total", max(0, time() - strtotime($row['added'])) * 1000);
-
- try {
- $sql = "INSERT INTO syncUploadProcessLog
- (userID, dataLength, processorHost, processDuration, totalDuration, error)
- VALUES (?,?,INET_ATON(?),?,?,?)";
- Zotero_DB::query(
- $sql,
- array(
- $row['userID'],
- $row['dataLength'],
- $host,
- round((float) microtime(true) - $startedTimestamp, 2),
- max(0, min(time() - strtotime($row['added']), 65535)),
- 0
- )
- );
- }
- catch (Exception $e) {
- Z_Core::logError($e);
- }
-
- try {
- self::processPostWriteLog($row['syncUploadQueueID'], $row['userID'], $timestamp);
- }
- catch (Exception $e) {
- Z_Core::logError($e);
- }
-
- // Delete stored data if necessary
- if ($queueDataID) {
- self::removeQueueData('upload', $queueDataID);
- }
- }
- // Timeout/connection error
- else if (
- strpos($msg, "Lock wait timeout exceeded; try restarting transaction") !== false
- || strpos($msg, "Deadlock found when trying to get lock; try restarting transaction") !== false
- || strpos($msg, "Too many connections") !== false
- || strpos($msg, "Can't connect to MySQL server") !==false
- || strpos($msg, "Connection refused") !==false
- || strpos($msg, "Connection timed out") !==false
- || $code == Z_ERROR_LIBRARY_TIMESTAMP_ALREADY_USED
- || $code == Z_ERROR_SHARD_READ_ONLY
- || $code == Z_ERROR_SHARD_UNAVAILABLE) {
- Z_Core::logError($e);
- $sql = "UPDATE syncUploadQueue SET started=NULL, tries=tries+1 WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, $row['syncUploadQueueID']);
- $lockError = true;
- StatsD::increment("sync.process.upload.errorTemporary");
- }
- // Save error
- else {
- // As of PHP 5.3.2 we can't serialize objects containing SimpleXMLElements,
- // and since the stack trace includes one, we have to catch this and
- // manually reconstruct an exception
- $serialized = serialize(
- new Exception(
- // Strip invalid \xa0 (due to note sanitizing and ?)
- iconv("utf-8", "utf-8//IGNORE", $msg),
- $e->getCode()
- )
- );
-
- Z_Core::logError($e);
- $sql = "UPDATE syncUploadQueue SET finished=?, errorCode=?, errorMessage=? WHERE syncUploadQueueID=?";
- Zotero_DB::query(
- $sql,
- array(
- Zotero_DB::getTransactionTimestamp(),
- $e->getCode(),
- $serialized,
- $row['syncUploadQueueID']
- )
- );
-
- StatsD::increment("sync.process.upload.errorPermanent");
-
- try {
- $sql = "INSERT INTO syncUploadProcessLog
- (userID, dataLength, processorHost, processDuration, totalDuration, error)
- VALUES (?,?,INET_ATON(?),?,?,?)";
- Zotero_DB::query(
- $sql,
- array(
- $row['userID'],
- $row['dataLength'],
- $host,
- round((float) microtime(true) - $startedTimestamp, 2),
- max(0, min(time() - strtotime($row['added']), 65535)),
- 1
- )
- );
- }
- catch (Exception $e) {
- Z_Core::logError($e);
- }
- }
-
- // Clear read locks
- $sql = "DELETE FROM syncUploadQueueLocks WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, $row['syncUploadQueueID']);
-
- Zotero_DB::commit();
-
- if ($lockError) {
- return -1;
- }
- else if ($error) {
- return -2;
- }
-
- return 1;
- }
-
-
- public static function checkUploadForErrors($syncProcessID) {
- Zotero_DB::beginTransaction();
-
- if (Z_Core::probability(30)) {
- $sql = "UPDATE syncUploadQueue SET started=NULL, errorCheck=0 WHERE started IS NOT NULL AND errorCheck=1 AND
- started < (NOW() - INTERVAL 15 MINUTE) AND finished IS NULL";
- Zotero_DB::query($sql);
- }
-
- // Get a queued process that hasn't been error-checked and is large enough to warrant it
- $sql = "SELECT * FROM syncUploadQueue WHERE started IS NULL AND errorCheck=0
- AND dataLength>=" . self::$minErrorCheckSize . " ORDER BY added LIMIT 1 FOR UPDATE";
- $row = Zotero_DB::rowQuery($sql);
-
- // No pending processes
- if (!$row) {
- Zotero_DB::commit();
- return 0;
- }
-
- $sql = "UPDATE syncUploadQueue SET started=NOW(), errorCheck=1 WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, array($row['syncUploadQueueID']));
-
- // We track error processes as upload processes that just get reset back to
- // started=NULL on completion (but with errorCheck=2)
- self::addUploadProcess($row['userID'], null, $row['syncUploadQueueID'], $syncProcessID);
-
- Zotero_DB::commit();
-
- try {
- $doc = new DOMDocument();
- $doc->loadXML($row['xmldata'], LIBXML_COMPACT | LIBXML_PARSEHUGE);
-
- // Get long tags
- $value = Zotero_Tags::getLongDataValueFromXML($doc);
- if ($value) {
- throw new Exception("Tag '" . $value . "' too long", Z_ERROR_TAG_TOO_LONG);
- }
-
- // Get long collection names
- $value = Zotero_Collections::getLongDataValueFromXML($doc);
- if ($value) {
- throw new Exception("Collection '" . $value . "' too long", Z_ERROR_COLLECTION_TOO_LONG);
- }
-
- // Get long creator names
- $node = Zotero_Creators::getLongDataValueFromXML($doc); // returns DOMNode rather than value
- if ($node) {
- $name = mb_substr($node->nodeValue, 0, 50);
- throw new Exception("=The name ‘{$name}…’ is too long to sync.\n\n"
- . "Search for the item with this name and shorten it. "
- . "Note that the item may be in the trash or in a group library.\n\n"
- . "If you receive this message repeatedly for items saved from a "
- . "particular site, you can report this issue in the Zotero Forums.",
- Z_ERROR_CREATOR_TOO_LONG);
- }
-
- // Get long item data fields
- $node = Zotero_Items::getLongDataValueFromXML($doc); // returns DOMNode rather than value
- if ($node) {
- $libraryID = $node->parentNode->getAttribute('libraryID');
- $key = $node->parentNode->getAttribute('key');
- if ($libraryID) {
- $key = $libraryID . "/" . $key;
- }
- $fieldName = $node->getAttribute('name');
- $fieldName = Zotero_ItemFields::getLocalizedString(null, $fieldName);
- if ($fieldName) {
- $start = "$fieldName field";
- }
- else {
- $start = "Field";
- }
- throw new Exception(
- "=$start value '" . mb_substr($node->nodeValue, 0, 75)
- . "...' too long for item '$key'", Z_ERROR_FIELD_TOO_LONG
- );
- }
- }
- catch (Exception $e) {
- //Z_Core::logError($e);
-
- Zotero_DB::beginTransaction();
-
- $sql = "UPDATE syncUploadQueue SET syncProcessID=NULL, finished=?,
- errorCode=?, errorMessage=? WHERE syncUploadQueueID=?";
- Zotero_DB::query(
- $sql,
- array(
- Zotero_DB::getTransactionTimestamp(),
- $e->getCode(),
- serialize($e),
- $row['syncUploadQueueID']
- )
- );
-
- $sql = "DELETE FROM syncUploadQueueLocks WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, $row['syncUploadQueueID']);
-
- self::removeUploadProcess($syncProcessID);
-
- Zotero_DB::commit();
-
- return -2;
- }
-
- Zotero_DB::beginTransaction();
-
- $sql = "UPDATE syncUploadQueue SET syncProcessID=NULL, started=NULL, errorCheck=2 WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, $row['syncUploadQueueID']);
-
- self::removeUploadProcess($syncProcessID);
-
- Zotero_DB::commit();
-
- return 1;
- }
-
-
- public static function getUploadQueueIDByUserID($userID) {
- $sql = "SELECT syncUploadQueueID FROM syncUploadQueue WHERE userID=?";
- return Zotero_DB::valueQuery($sql, $userID);
- }
-
-
- public static function postWriteLog($syncUploadQueueID, $objectType, $id, $action) {
- $sql = "INSERT IGNORE INTO syncUploadQueuePostWriteLog VALUES (?,?,?,?)";
- Zotero_DB::query($sql, array($syncUploadQueueID, $objectType, $id, $action));
- }
-
-
- public static function processPostWriteLog($syncUploadQueueID, $userID, $timestamp) {
- // Increase timestamp by a second past the time of the queued process
- $timestamp++;
-
- $sql = "SELECT * FROM syncUploadQueuePostWriteLog WHERE syncUploadQueueID=?";
- $entries = Zotero_DB::query($sql, $syncUploadQueueID);
- foreach ($entries as $entry) {
- switch ($entry['objectType']) {
- case 'group':
- switch ($entry['action']) {
- case 'update':
- $sql = "UPDATE groups SET dateModified=FROM_UNIXTIME(?) WHERE groupID=?";
- $affected = Zotero_DB::query($sql, array($timestamp, $entry['ids']));
- break;
-
- case 'delete':
- $sql = "UPDATE syncDeleteLogIDs SET timestamp=FROM_UNIXTIME(?)
- WHERE libraryID=? AND objectType='group' AND id=?";
- $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
- $affected = Zotero_DB::query(
- $sql,
- array($timestamp, $userLibraryID, $entry['ids']),
- Zotero_Shards::getByLibraryID($userLibraryID)
- );
- break;
-
- default:
- throw new Exception("Unsupported action {$entry['action']} for type {$entry['objectType']}");
- }
- break;
-
- case 'groupUser':
- // If the affected user isn't the queued user, this isn't necessary
- list ($groupID, $groupUserID) = explode('-', $entry['ids']);
- if ($userID != $groupUserID) {
- throw new Exception("Upload user is not logged user");
- }
-
- switch ($entry['action']) {
- case 'update':
- $sql = "UPDATE groupUsers SET lastUpdated=FROM_UNIXTIME(?) WHERE groupID=? AND userID=?";
- $affected = Zotero_DB::query($sql, array($timestamp, $groupID, $userID));
- break;
-
- case 'delete':
- $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
- $sql = "UPDATE syncDeleteLogIDs SET timestamp=FROM_UNIXTIME(?)
- WHERE libraryID=? AND objectType='group' AND id=?";
- $affected = Zotero_DB::query(
- $sql,
- array($timestamp, $userLibraryID, $groupID),
- Zotero_Shards::getByLibraryID($userLibraryID)
- );
- break;
-
- default:
- throw new Exception("Unsupported action {$entry['action']} for type {$entry['objectType']}");
- }
- break;
-
- default:
- throw new Exception ("Unknown object type {$entry['objectType']}");
- }
-
- if ($affected == 0) {
- Z_Core::logError(
- "Post-queue write "
- . "{$entry['syncUploadQueueID']}/"
- . "{$entry['objectType']}/"
- . "{$entry['ids']}/"
- . "{$entry['action']}"
- . " didn't change any rows"
- );
- }
- }
- }
-
-
- public static function countQueuedDownloadProcesses() {
- $sql = "SELECT COUNT(*) FROM syncDownloadQueue WHERE started IS NULL";
- return Zotero_DB::valueQuery($sql);
- }
-
-
- public static function countQueuedUploadProcesses($errorCheck=false) {
- $sql = "SELECT COUNT(*) FROM syncUploadQueue WHERE started IS NULL";
- // errorCheck=0 indicates that the upload has not been checked for errors
- if ($errorCheck) {
- $sql .= " AND errorCheck=0 AND dataLength>5000";
- }
- return Zotero_DB::valueQuery($sql);
- }
-
-
- public static function getOldDownloadProcesses($host=null, $seconds=60) {
- $sql = "SELECT syncDownloadProcessID FROM syncDownloadQueue
- WHERE started < NOW() - INTERVAL ? SECOND";
- $params = array($seconds);
- if ($host) {
- $sql .= " AND processorHost=INET_ATON(?)";
- $params[] = $host;
- }
- return Zotero_DB::columnQuery($sql, $params);
- }
-
-
- public static function getOldUploadProcesses($host, $seconds=60) {
- $sql = "SELECT syncProcessID FROM syncUploadQueue
- WHERE started < NOW() - INTERVAL ? SECOND AND errorCheck!=1";
- $params = array($seconds);
- if ($host) {
- $sql .= " AND processorHost=INET_ATON(?)";
- $params[] = $host;
- }
- return Zotero_DB::columnQuery($sql, $params);
- }
-
-
- public static function getOldErrorProcesses($host, $seconds=60) {
- $sql = "SELECT syncProcessID FROM syncUploadQueue
- WHERE started < NOW() - INTERVAL ? SECOND AND errorCheck=1";
- $params = array($seconds);
- if ($host) {
- $sql .= " AND processorHost=INET_ATON(?)";
- $params[] = $host;
- }
- return Zotero_DB::columnQuery($sql, $params);
- }
-
-
- /**
- * Remove process id from process in database
- */
- public static function removeDownloadProcess($syncDownloadProcessID) {
- $sql = "UPDATE syncDownloadQueue SET syncDownloadProcessID=NULL
- WHERE syncDownloadProcessID=?";
- Zotero_DB::query($sql, $syncDownloadProcessID);
- }
-
-
- /**
- * Remove upload process and locks from database
- */
- public static function removeUploadProcess($syncProcessID) {
- $sql = "DELETE FROM syncProcesses WHERE syncProcessID=?";
- Zotero_DB::query($sql, $syncProcessID);
- }
-
-
- /**
- * Purge error process from database and reset errorCheck to 0
- *
- * This is called only after an error check is orphaned
- */
- public static function purgeErrorProcess($syncErrorProcessID) {
- Zotero_DB::beginTransaction();
-
- self::removeUploadProcess($syncErrorProcessID);
-
- $sql = "UPDATE syncUploadQueue SET errorCheck=0 WHERE syncProcessID=?";
- Zotero_DB::query($sql, $syncErrorProcessID);
-
- Zotero_DB::commit();
- }
-
-
- public static function getCachedDownload($userID, $lastsync, $apiVersion, $cacheKeyExtra="") {
- if (!$lastsync) {
- throw new Exception('$lastsync not provided');
- }
-
- $s3Client = Z_Core::$AWS->createS3();
-
- $s3Key = $apiVersion . "/" . md5(
- Zotero_Users::getUpdateKey($userID)
- . "_" . $lastsync
- // Remove after 2.1 sync cutoff
- . ($apiVersion >= 9 ? "_" . $apiVersion : "")
- . "_" . self::$cacheVersion
- . (!empty($cacheKeyExtra) ? "_" . $cacheKeyExtra : "")
- );
-
- // Check S3 for file
- try {
- try {
- $result = $s3Client->getObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key
- ]);
- $xmldata = (string) $result['Body'];
- }
- catch (Aws\S3\Exception\S3Exception $e) {
- if ($e->getAwsErrorCode() == 'NoSuchKey') {
- $xmldata = false;
- }
- else {
- throw $e;
- }
- }
- }
- catch (Exception $e) {
- Z_Core::logError("Warning: '" . $e . "' getting cached download from S3");
- $xmldata = false;
- }
-
- // Update the last-used timestamp in S3
- if ($xmldata) {
- try {
- $s3Client->copyObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key,
- 'CopySource' => Z_CONFIG::$S3_BUCKET_CACHE . "/" . $s3Key,
- 'Metadata' => [
- 'last-used' => time()
- ],
- 'MetadataDirective' => 'REPLACE'
- ]);
- }
- catch (Exception $e) {
- error_log("WARNING: " . $e);
- }
- }
-
- return $xmldata;
- }
-
-
- public static function cacheDownload($userID, $updateKey, $lastsync, $apiVersion, $xmldata, $cacheKeyExtra="") {
- $s3Client = Z_Core::$AWS->createS3();
-
- $s3Key = $apiVersion . "/" . md5(
- $updateKey . "_" . $lastsync
- // Remove after 2.1 sync cutoff
- . ($apiVersion >= 9 ? "_" . $apiVersion : "")
- . "_" . self::$cacheVersion
- . (!empty($cacheKeyExtra) ? "_" . $cacheKeyExtra : "")
- );
-
- // Add to S3
- $response = $s3Client->putObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key,
- 'Body' => $xmldata
- ]);
- }
-
-
- public static function getQueueDataIDFromField($field) {
- if (substr($field, 0, 7) == 'STORED:') {
- return substr($field, 7);
- }
- return false;
- }
-
-
- public static function getQueueData($type, $queueDataID) {
- $s3Client = Z_Core::$AWS->createS3();
- $s3Key = "sync/$type/$queueDataID";
-
- $tries = 0;
- $maxTries = 5;
- while (true) {
- try {
- $result = $s3Client->getObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key
- ]);
- break;
- }
- catch (Exception $e) {
- $tries++;
- if ($tries >= $maxTries) {
- throw new Exception($e);
- }
- error_log("WARNING: " . $e);
- sleep(pow(2, $tries));
- continue;
- }
- }
-
- return (string) $result['Body'];
- }
-
-
- public static function storeQueueData($type, $xmldata) {
- if (strlen($xmldata) < self::$queueDataThreshold) {
- return $xmldata;
- }
-
- $s3Client = Z_Core::$AWS->createS3();
- $queueDataID = Zotero_Utilities::randomString(32, 'mixed', true);
- $s3Key = "sync/$type/$queueDataID";
-
- $tries = 0;
- $maxTries = 5;
- while (true) {
- try {
- $s3Client->putObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key,
- 'Body' => $xmldata
- ]);
- }
- catch (Exception $e) {
- $tries++;
- if ($tries >= $maxTries) {
- throw new Exception($e);
- }
- error_log("WARNING: " . $e);
- sleep(pow(2, $tries));
- continue;
- }
- break;
- }
-
- return "STORED:" . $queueDataID;
- }
-
-
- public static function removeQueueData($type, $queueDataID) {
- $s3Client = Z_Core::$AWS->createS3();
- $s3Key = "sync/$type/$queueDataID";
-
- try {
- $s3Client->deleteObject([
- 'Bucket' => Z_CONFIG::$S3_BUCKET_CACHE,
- 'Key' => $s3Key
- ]);
- }
- catch (Exception $e) {
- error_log("WARNING: " . $e);
- }
- }
-
-
- /**
- * Get the result of a queued download process for a given sync session
- *
- * If still queued, return false
- * If success, return " "123456789.1234", 'exception' => Exception)
- * If not queued, return -1
- */
- public static function getSessionDownloadResult($sessionID) {
- Zotero_DB::beginTransaction();
- $sql = "SELECT syncDownloadQueueID, finished, xmldata, errorCode, errorMessage "
- . "FROM syncDownloadQueue WHERE sessionID=?";
- $row = Zotero_DB::rowQuery($sql, $sessionID);
- if (!$row) {
- Zotero_DB::commit();
- return -1;
- }
-
- if (is_null($row['finished'])) {
- // Every two minutes, update lastCheck
- if (!Z_Core::$MC->get("syncDownloadLastCheck_$sessionID")) {
- $sql = "UPDATE syncDownloadQueue SET lastCheck=NOW() WHERE sessionID=?";
- Zotero_DB::query($sql, $sessionID);
-
- Z_Core::$MC->set("syncDownloadLastCheck_$sessionID", true, 120);
- }
- Zotero_DB::commit();
- return false;
- }
-
- // On success, get download data from S3 or DB
- if (is_null($row['errorCode'])) {
- $queueDataID = self::getQueueDataIDFromField($row['xmldata']);
-
- // S3
- if ($queueDataID) {
- $xmldata = self::getQueueData('download', $queueDataID);
- }
- // DB
- else {
- $queueDataID = false;
- $xmldata = $row['xmldata'];
- }
- }
-
- $sql = "DELETE FROM syncDownloadQueue WHERE sessionID=?";
- Zotero_DB::query($sql, $sessionID);
- Zotero_DB::commit();
-
- // Success
- if (is_null($row['errorCode'])) {
- // Delete stored data if necessary
- if ($queueDataID) {
- self::removeQueueData('download', $queueDataID);
- }
- return $xmldata;
- }
-
- $e = @unserialize($row['errorMessage']);
-
- // In case it's not a valid exception for some reason, make one
- if (!($e instanceof Exception)) {
- $e = new Exception($row['errorMessage'], $row['errorCode']);
- }
-
- throw ($e);
- }
-
-
- /**
- * Get the result of a queued process for a given sync session
- *
- * If no result, return false
- * If success, return array('timestamp' => "123456789")
- * If error, return array('xmldata' => " Exception)
- */
- public static function getSessionUploadResult($sessionID) {
- Zotero_DB::beginTransaction();
- $sql = "SELECT UNIX_TIMESTAMP(finished) AS finished, xmldata, errorCode, errorMessage
- FROM syncUploadQueue WHERE sessionID=?";
- $row = Zotero_DB::rowQuery($sql, $sessionID);
- if (!$row) {
- Zotero_DB::commit();
- throw new Exception("Queued upload not found for session");
- }
-
- if (is_null($row['finished'])) {
- Zotero_DB::beginTransaction();
- return false;
- }
-
- $sql = "DELETE FROM syncUploadQueue WHERE sessionID=?";
- Zotero_DB::query($sql, $sessionID);
- Zotero_DB::commit();
-
- // Success
- if (is_null($row['errorCode'])) {
- return array('timestamp' => $row['finished']);
- }
-
- // On failure, get XML data from cache for error report and clear from cache
- $queueDataID = self::getQueueDataIDFromField($row['xmldata']);
- if ($queueDataID) {
- $row['xmldata'] = self::getQueueData('upload', $queueDataID);
- self::removeQueueData('upload', $queueDataID);
- }
-
- $e = @unserialize($row['errorMessage']);
-
- return array('timestamp' => $row['finished'], 'xmldata' => $row['xmldata'], 'exception' => $e);
- }
-
-
- public static function logDownload($userID, $lastsync, $object, $ipAddress, $host, $processDuration, $totalDuration, $error) {
- try {
- if (is_numeric($ipAddress)) {
- $ipParam = "?";
- }
- else {
- $ipParam = "INET_ATON(?)";
- }
-
- $sql = "INSERT INTO syncDownloadProcessLog
- (userID, lastsync, objects, ipAddress, processorHost, processDuration, totalDuration, error)
- VALUES (?,FROM_UNIXTIME(?),?,$ipParam,INET_ATON(?),?,?,?)";
- Zotero_DB::query(
- $sql,
- array(
- $userID,
- $lastsync,
- $object,
- $ipAddress,
- $host,
- $processDuration,
- $totalDuration,
- $error
- )
- );
- }
- catch (Exception $e) {
- Z_Core::logError($e);
- }
- }
-
-
- //
- //
- // Private methods
- //
- //
- private static function processDownloadInternal($userID, $lastsync, DOMDocument $doc, $syncDownloadQueueID=null, $syncDownloadProcessID=null, $params=[]) {
- $apiVersion = (int) $doc->documentElement->getAttribute('version');
-
- if ($lastsync == 1) {
- StatsD::increment("sync.process.download.full");
- }
-
- // TEMP
- $cacheKeyExtra = (!empty($params['ft']) ? json_encode($params['ft']) : "")
- . (!empty($params['ftkeys']) ? json_encode($params['ftkeys']) : "");
-
- try {
- $cached = Zotero_Sync::getCachedDownload($userID, $lastsync, $apiVersion, $cacheKeyExtra);
- if ($cached) {
- $doc->loadXML($cached);
- StatsD::increment("sync.process.download.cache.hit");
- return;
- }
- }
- catch (Exception $e) {
- $msg = $e->getMessage();
- if (strpos($msg, "Too many connections") !== false) {
- $msg = "'Too many connections' from MySQL";
- }
- else {
- $msg = "'$msg'";
- }
- Z_Core::logError("Warning: $msg getting cached download");
- StatsD::increment("sync.process.download.cache.error");
- }
-
- set_time_limit(1800);
-
- $profile = false;
- if ($profile) {
- $shardID = Zotero_Shards::getByUserID($userID);
- Zotero_DB::profileStart(0);
- }
-
- if ($syncDownloadQueueID) {
- self::addDownloadProcess($syncDownloadQueueID, $syncDownloadProcessID);
- }
-
- $updatedNode = $doc->createElement('updated');
- $doc->documentElement->appendChild($updatedNode);
-
- $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
-
- $updatedCreators = array();
-
- try {
- Zotero_DB::beginTransaction();
-
- // Blocks until any upload processes are done
- $updateTimes = Zotero_Libraries::getUserLibraryUpdateTimes($userID);
-
- $timestamp = Zotero_DB::getTransactionTimestampUnix();
- $doc->documentElement->setAttribute('timestamp', $timestamp);
-
- $doc->documentElement->setAttribute('userID', $userID);
- $doc->documentElement->setAttribute('defaultLibraryID', $userLibraryID);
- $updateKey = Zotero_Users::getUpdateKey($userID);
- $doc->documentElement->setAttribute('updateKey', $updateKey);
-
- // Get libraries with update times >= $timestamp
- $updatedLibraryIDs = array();
- foreach ($updateTimes as $libraryID=>$timestamp) {
- if ($timestamp >= $lastsync) {
- $updatedLibraryIDs[] = $libraryID;
- }
- }
-
- // Add new and updated groups
- $joinedGroups = Zotero_Groups::getJoined($userID, (int) $lastsync);
- $updatedIDs = array_unique(array_merge(
- $joinedGroups, Zotero_Groups::getUpdated($userID, (int) $lastsync)
- ));
- if ($updatedIDs) {
- $node = $doc->createElement('groups');
- $showGroups = false;
-
- foreach ($updatedIDs as $id) {
- $group = new Zotero_Group;
- $group->id = $id;
- $xmlElement = $group->toXML($userID);
-
- $newNode = dom_import_simplexml($xmlElement);
- $newNode = $doc->importNode($newNode, true);
- $node->appendChild($newNode);
- $showGroups = true;
- }
-
- if ($showGroups) {
- $updatedNode->appendChild($node);
- }
- }
-
- // If there's updated data in any library or
- // there are any new groups (in which case we need all their data)
- $hasData = $updatedLibraryIDs || $joinedGroups;
- if ($hasData) {
- foreach (Zotero_DataObjects::$classicObjectTypes as $syncObject) {
- $Name = $syncObject['singular']; // 'Item'
- $Names = $syncObject['plural']; // 'Items'
- $name = strtolower($Name); // 'item'
- $names = strtolower($Names); // 'items'
-
- $className = 'Zotero_' . $Names;
-
- $updatedIDsByLibraryID = call_user_func(array($className, 'getUpdated'), $userID, $lastsync, $updatedLibraryIDs);
- if ($updatedIDsByLibraryID) {
- $node = $doc->createElement($names);
- foreach ($updatedIDsByLibraryID as $libraryID=>$ids) {
- if ($name == 'creator') {
- $updatedCreators[$libraryID] = $ids;
- }
-
- foreach ($ids as $id) {
- if ($name == 'item') {
- $obj = call_user_func(array($className, 'get'), $libraryID, $id);
- $data = array(
- 'updatedCreators' => isset($updatedCreators[$libraryID]) ? $updatedCreators[$libraryID] : array()
- );
- $xmlElement = Zotero_Items::convertItemToXML($obj, $data, $apiVersion);
- }
- else {
- $instanceClass = 'Zotero_' . $Name;
- $obj = new $instanceClass;
- if (method_exists($instanceClass, '__construct')) {
- $obj->__construct();
- }
- $obj->libraryID = $libraryID;
- if ($name == 'setting') {
- $obj->name = $id;
- }
- else {
- $obj->id = $id;
- }
- if ($name == 'tag') {
- $xmlElement = call_user_func(array($className, "convert{$Name}ToXML"), $obj, true);
- }
- else if ($name == 'creator') {
- $xmlElement = call_user_func(array($className, "convert{$Name}ToXML"), $obj, $doc);
- if ($xmlElement->getAttribute('libraryID') == $userLibraryID) {
- $xmlElement->removeAttribute('libraryID');
- }
- $node->appendChild($xmlElement);
- }
- else if ($name == 'relation') {
- // Skip new-style related items
- if ($obj->predicate == 'dc:relation') {
- continue;
- }
- $xmlElement = call_user_func(array($className, "convert{$Name}ToXML"), $obj);
- if ($apiVersion <= 8) {
- unset($xmlElement['libraryID']);
- }
- }
- else if ($name == 'setting') {
- $xmlElement = call_user_func(array($className, "convert{$Name}ToXML"), $obj, $doc);
- $node->appendChild($xmlElement);
- }
- else {
- $xmlElement = call_user_func(array($className, "convert{$Name}ToXML"), $obj);
- }
- }
-
- if ($xmlElement instanceof SimpleXMLElement) {
- if ($xmlElement['libraryID'] == $userLibraryID) {
- unset($xmlElement['libraryID']);
- }
-
- $newNode = dom_import_simplexml($xmlElement);
- $newNode = $doc->importNode($newNode, true);
- $node->appendChild($newNode);
- }
- }
- }
- if ($node->hasChildNodes()) {
- $updatedNode->appendChild($node);
- }
- }
- }
- }
-
- // Add full-text content if the client supports it
- if (isset($params['ft'])) {
- $libraries = Zotero_Libraries::getUserLibraries($userID);
- $fulltextNode = false;
- foreach ($libraries as $libraryID) {
- if (!empty($params['ftkeys']) && $params['ftkeys'] === 'all') {
- $ftlastsync = 1;
- }
- else {
- $ftlastsync = $lastsync;
- }
- if (!empty($params['ftkeys'][$libraryID])) {
- $keys = $params['ftkeys'][$libraryID];
- }
- else {
- $keys = [];
- }
- $data = Zotero_FullText::getNewerInLibraryByTime($libraryID, $ftlastsync, $keys);
- if ($data) {
- if (!$fulltextNode) {
- $fulltextNode = $doc->createElement('fulltexts');
- }
- foreach ($data as $itemData) {
- if ($params['ft']) {
- $empty = $itemData['empty'];
- }
- // If full-text syncing is disabled, leave content empty
- else {
- $empty = true;
- }
- $first = false;
- $node = Zotero_FullText::itemDataToXML($itemData, $doc, $empty);
- $fulltextNode->appendChild($node);
- }
- }
- }
- if ($fulltextNode) {
- $updatedNode->appendChild($fulltextNode);
- }
- }
-
- // Get earliest timestamp
- $earliestModTime = Zotero_Users::getEarliestDataTimestamp($userID);
- $doc->documentElement->setAttribute('earliest', $earliestModTime ? $earliestModTime : 0);
-
- // Deleted objects
- $deletedKeys = $hasData ? self::getDeletedObjectKeys($userID, $lastsync, true) : false;
- $deletedIDs = self::getDeletedObjectIDs($userID, $lastsync, true);
- if ($deletedKeys || $deletedIDs) {
- $deletedNode = $doc->createElement('deleted');
-
- // Add deleted data objects
- if ($deletedKeys) {
- foreach (Zotero_DataObjects::$classicObjectTypes as $syncObject) {
- $Name = $syncObject['singular']; // 'Item'
- $Names = $syncObject['plural']; // 'Items'
- $name = strtolower($Name); // 'item'
- $names = strtolower($Names); // 'items'
-
- if (empty($deletedKeys[$names])) {
- continue;
- }
-
- $typeNode = $doc->createElement($names);
-
- foreach ($deletedKeys[$names] as $row) {
- $node = $doc->createElement($name);
- if ($row['libraryID'] != $userLibraryID || $name == 'setting') {
- $node->setAttribute('libraryID', $row['libraryID']);
- }
- $node->setAttribute('key', $row['key']);
- $typeNode->appendChild($node);
- }
- $deletedNode->appendChild($typeNode);
- }
- }
-
- // Add deleted groups
- if ($deletedIDs) {
- $name = "group";
- $names = "groups";
-
- $typeNode = $doc->createElement($names);
- $ids = $doc->createTextNode(implode(' ', $deletedIDs[$names]));
- $typeNode->appendChild($ids);
- $deletedNode->appendChild($typeNode);
- }
-
- $updatedNode->appendChild($deletedNode);
- }
-
- Zotero_DB::commit();
- }
- catch (Exception $e) {
- Zotero_DB::rollback(true);
- if ($syncDownloadQueueID) {
- self::removeDownloadProcess($syncDownloadProcessID);
- }
- throw ($e);
- }
-
- function relaxNGErrorHandler($errno, $errstr) {
- Zotero_Sync::$validationError = $errstr;
- }
- set_error_handler('relaxNGErrorHandler');
- $valid = $doc->relaxNGValidate(Z_ENV_MODEL_PATH . 'relax-ng/updated.rng');
- restore_error_handler();
- if (!$valid) {
- if ($syncDownloadQueueID) {
- self::removeDownloadProcess($syncDownloadProcessID);
- }
- throw new Exception(self::$validationError . "\n\nXML:\n\n" . $doc->saveXML());
- }
-
- // Cache response if response isn't empty
- try {
- if ($doc->documentElement->firstChild->hasChildNodes()) {
- self::cacheDownload($userID, $updateKey, $lastsync, $apiVersion, $doc->saveXML(), $cacheKeyExtra);
- }
- }
- catch (Exception $e) {
- Z_Core::logError("WARNING: " . $e);
- }
-
- if ($syncDownloadQueueID) {
- self::removeDownloadProcess($syncDownloadProcessID);
- }
-
- if ($profile) {
- $shardID = Zotero_Shards::getByUserID($userID);
- Zotero_DB::profileEnd(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();
- $skipCreators = [];
-
- // 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) === '') {
- // Remember the empty creator, since it might be referenced without
- // data within an item and would otherwise be missing
- if (empty($skipCreators[$creatorObj->libraryID])) {
- $skipCreators[$creatorObj->libraryID] = [];
- }
- $skipCreators[$creatorObj->libraryID][] = $creatorObj->key;
- 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, $skipCreators);
-
- 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;
- }
- }
- // Zotero_Item
- 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;
- }
- }
-
-
- public static function getTimestampParts($timestamp) {
- $float = explode('.', $timestamp);
- $timestamp = $float[0];
- $timestampMS = isset($float[1]) ? substr($float[1], 0, 5) : 0;
- if ($timestampMS > 65535) {
- $timestampMS = substr($float[1], 0, 4);
- }
- return array($timestamp, (int) $timestampMS);
- }
-
-
-
- /**
- * Recursively save collections from the top down
- */
- private static function saveCollections($collections, $userID) {
- $originalLength = sizeOf($collections);
- $unsaved = array();
-
- $toSave = array();
- for ($i=0, $len=sizeOf($collections); $i<$len; $i++) {
- $toSave[$collections[$i]->key] = true;
- }
- for ($i=0; $ikey;
-
- $parentKey = $collection->parentKey;
- // Top-level collection, so save
- if (!$parentKey) {
- $collection->save($userID);
- unset($toSave[$key]);
- continue;
- }
- $parentCollection = Zotero_Collections::getByLibraryAndKey($collection->libraryID, $parentKey);
- // Parent collection exists and doesn't need to be saved, so save
- if ($parentCollection && empty($toSave[$parentCollection->key])) {
- $collection->save($userID);
- unset($toSave[$key]);
- continue;
- }
- // Add to unsaved list
- $unsaved[] = $collection;
- continue;
- }
-
- if ($unsaved) {
- if ($originalLength == sizeOf($unsaved)) {
- throw new Exception("Incomplete collection hierarchy cannot be saved", Z_ERROR_COLLECTION_NOT_FOUND);
- }
-
- self::saveCollections($unsaved, $userID);
- }
- }
-
-
- private static function addDownloadProcess($syncDownloadQueueID, $syncDownloadProcessID) {
- $sql = "UPDATE syncDownloadQueue SET syncDownloadProcessID=? WHERE syncDownloadQueueID=?";
- Zotero_DB::query($sql, array($syncDownloadProcessID, $syncDownloadQueueID));
- }
-
- /**
- * Add sync process and associated locks to database
- */
- private static function addUploadProcess($userID, $libraryIDs, $syncQueueID=null, $syncProcessID=null) {
- Zotero_DB::beginTransaction();
-
- $syncProcessID = $syncProcessID ? $syncProcessID : Zotero_ID::getBigInt();
- $sql = "INSERT INTO syncProcesses (syncProcessID, userID) VALUES (?, ?)";
- try {
- Zotero_DB::query($sql, array($syncProcessID, $userID));
- }
- catch (Exception $e) {
- $sql = "SELECT CONCAT(syncProcessID,' ',userID,' ',started) FROM syncProcesses WHERE userID=?";
- $val = Zotero_DB::valueQuery($sql, $userID);
- Z_Core::logError($val);
- }
-
- if ($libraryIDs) {
- $sql = "INSERT INTO syncProcessLocks VALUES ";
- $sql .= implode(', ', array_fill(0, sizeOf($libraryIDs), '(?,?)'));
- $params = array();
- foreach ($libraryIDs as $libraryID) {
- $params[] = $syncProcessID;
- $params[] = $libraryID;
- }
- Zotero_DB::query($sql, $params);
- }
-
- // Record the process id in the queue entry, if given
- if ($syncQueueID) {
- $sql = "UPDATE syncUploadQueue SET syncProcessID=? WHERE syncUploadQueueID=?";
- Zotero_DB::query($sql, array($syncProcessID, $syncQueueID));
- }
-
- Zotero_DB::commit();
-
- return $syncProcessID;
- }
-
-
- private static function countDeletedObjectKeys($userID, $timestamp, $updatedLibraryIDs) {
- /*
- $sql = "SELECT version FROM version WHERE schema='syncdeletelog'";
- $syncLogStart = Zotero_DB::valueQuery($sql);
- if (!$syncLogStart) {
- throw ('Sync log start time not found');
- }
- */
-
- /*
- // Last sync time is before start of log
- if ($lastSyncDate && new Date($syncLogStart * 1000) > $lastSyncDate) {
- return -1;
- }
- */
-
- $shardLibraryIDs = array();
-
- // Personal library
- $shardID = Zotero_Shards::getByUserID($userID);
- $libraryID = Zotero_Users::getLibraryIDFromUserID($userID);
- if (in_array($libraryID, $updatedLibraryIDs)) {
- $shardLibraryIDs[$shardID] = array($libraryID);
- }
-
- // Group libraries
- $groupIDs = Zotero_Groups::getUserGroups($userID);
- if ($groupIDs) {
- // Separate groups into shards for querying
- foreach ($groupIDs as $groupID) {
- $libraryID = Zotero_Groups::getLibraryIDFromGroupID($groupID);
- // If library hasn't changed, skip
- if (!in_array($libraryID, $updatedLibraryIDs)) {
- continue;
- }
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- if (!isset($shardLibraryIDs[$shardID])) {
- $shardLibraryIDs[$shardID] = array();
- }
- $shardLibraryIDs[$shardID][] = $libraryID;
- }
- }
-
- // Send query at each shard
- $rows = array();
- $count = 0;
- foreach ($shardLibraryIDs as $shardID=>$libraryIDs) {
- $sql = "SELECT COUNT(*) FROM syncDeleteLogKeys WHERE libraryID IN ("
- . implode(', ', array_fill(0, sizeOf($libraryIDs), '?'))
- . ") "
- // API only
- . "AND objectType != 'tagName'";
- $params = $libraryIDs;
- if ($timestamp) {
- $sql .= " AND timestamp >= FROM_UNIXTIME(?)";
- $params[] = $timestamp;
- }
- $count += Zotero_DB::valueQuery($sql, $params, $shardID);
- }
-
- return $count;
- }
-
-
- /**
- * @param int $userID User id
- * @param int $timestamp Unix timestamp of last sync
- * @return mixed Returns array of objects with properties
- * 'libraryID', 'id', and 'rowType' ('key' or 'id'),
- * FALSE if none, or -1 if last sync time is before start of log
- */
- private static function getDeletedObjectKeys($userID, $timestamp, $includeAllUserObjects=false) {
- /*
- $sql = "SELECT version FROM version WHERE schema='syncdeletelog'";
- $syncLogStart = Zotero_DB::valueQuery($sql);
- if (!$syncLogStart) {
- throw ('Sync log start time not found');
- }
- */
-
- /*
- // Last sync time is before start of log
- if ($lastSyncDate && new Date($syncLogStart * 1000) > $lastSyncDate) {
- return -1;
- }
- */
-
- // Personal library
- $shardID = Zotero_Shards::getByUserID($userID);
- $libraryID = Zotero_Users::getLibraryIDFromUserID($userID);
- $shardLibraryIDs[$shardID] = array($libraryID);
-
- // Group libraries
- if ($includeAllUserObjects) {
- $groupIDs = Zotero_Groups::getUserGroups($userID);
- if ($groupIDs) {
- // Separate groups into shards for querying
- foreach ($groupIDs as $groupID) {
- $libraryID = Zotero_Groups::getLibraryIDFromGroupID($groupID);
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- if (!isset($shardLibraryIDs[$shardID])) {
- $shardLibraryIDs[$shardID] = array();
- }
- $shardLibraryIDs[$shardID][] = $libraryID;
- }
- }
- }
-
- // Send query at each shard
- $rows = array();
- foreach ($shardLibraryIDs as $shardID=>$libraryIDs) {
- $sql = "SELECT libraryID, objectType, `key`, timestamp
- FROM syncDeleteLogKeys WHERE libraryID IN ("
- . implode(', ', array_fill(0, sizeOf($libraryIDs), '?'))
- . ")"
- // API only
- . " AND objectType != 'tagName'";
- $params = $libraryIDs;
- if ($timestamp) {
- $sql .= " AND timestamp >= FROM_UNIXTIME(?)";
- $params[] = $timestamp;
- }
- $sql .= " ORDER BY timestamp";
- $shardRows = Zotero_DB::query($sql, $params, $shardID);
- if ($shardRows) {
- $rows = array_merge($rows, $shardRows);
- }
- }
-
- if (!$rows) {
- return false;
- }
-
- $deletedIDs = array();
- foreach (Zotero_DataObjects::$classicObjectTypes as $syncObject) {
- $deletedIDs[strtolower($syncObject['plural'])] = array();
- }
- foreach ($rows as $row) {
- $type = strtolower(Zotero_DataObjects::$classicObjectTypes[$row['objectType']]['plural']);
- $deletedIDs[$type][] = array(
- 'libraryID' => $row['libraryID'],
- 'key' => $row['key']
- );
- }
-
- return $deletedIDs;
- }
-
-
- private static function getDeletedObjectIDs($userID, $timestamp, $includeAllUserObjects=false) {
- /*
- $sql = "SELECT version FROM version WHERE schema='syncdeletelog'";
- $syncLogStart = Zotero_DB::valueQuery($sql);
- if (!$syncLogStart) {
- throw ('Sync log start time not found');
- }
- */
-
- /*
- // Last sync time is before start of log
- if ($lastSyncDate && new Date($syncLogStart * 1000) > $lastSyncDate) {
- return -1;
- }
- */
-
- // Personal library
- $shardID = Zotero_Shards::getByUserID($userID);
- $libraryID = Zotero_Users::getLibraryIDFromUserID($userID);
- $shardLibraryIDs[$shardID] = array($libraryID);
-
- // Group libraries
- if ($includeAllUserObjects) {
- $groupIDs = Zotero_Groups::getUserGroups($userID);
- if ($groupIDs) {
- // Separate groups into shards for querying
- foreach ($groupIDs as $groupID) {
- $libraryID = Zotero_Groups::getLibraryIDFromGroupID($groupID);
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- if (!isset($shardLibraryIDs[$shardID])) {
- $shardLibraryIDs[$shardID] = array();
- }
- $shardLibraryIDs[$shardID][] = $libraryID;
- }
- }
- }
-
- // Send query at each shard
- $rows = array();
- foreach ($shardLibraryIDs as $shardID=>$libraryIDs) {
- $sql = "SELECT libraryID, objectType, id, timestamp
- FROM syncDeleteLogIDs WHERE libraryID IN ("
- . implode(', ', array_fill(0, sizeOf($libraryIDs), '?'))
- . ")";
- $params = $libraryIDs;
- if ($timestamp) {
- // Send any entries from before these were being properly sent
- if ($timestamp < 1260778500) {
- $sql .= " AND (timestamp >= FROM_UNIXTIME(?) OR timestamp BETWEEN 1257968068 AND FROM_UNIXTIME(?))";
- $params[] = $timestamp;
- $params[] = 1260778500;
- }
- else {
- $sql .= " AND timestamp >= FROM_UNIXTIME(?)";
- $params[] = $timestamp;
- }
- }
- $sql .= " ORDER BY timestamp";
-
- $shardRows = Zotero_DB::query($sql, $params, $shardID);
- if ($shardRows) {
- $rows = array_merge($rows, $shardRows);
- }
- }
-
- if (!$rows) {
- return false;
- }
-
- $deletedIDs = array(
- 'groups' => array()
- );
-
- foreach ($rows as $row) {
- $type = $row['objectType'] . 's';
- $deletedIDs[$type][] = $row['id'];
- }
-
- return $deletedIDs;
- }
-}
-?>
diff --git a/model/Tag.inc.php b/model/Tag.inc.php
deleted file mode 100644
index 432a93c2..00000000
--- a/model/Tag.inc.php
+++ /dev/null
@@ -1,782 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Tag {
- private $id;
- private $libraryID;
- private $key;
- private $name;
- private $type;
- private $dateAdded;
- private $dateModified;
- private $version;
-
- private $loaded;
- private $changed;
- private $previousData;
-
- private $linkedItemsLoaded = false;
- private $linkedItems = array();
-
- public function __construct() {
- $numArgs = func_num_args();
- if ($numArgs) {
- throw new Exception("Constructor doesn't take any parameters");
- }
-
- $this->init();
- }
-
-
- private function init() {
- $this->loaded = false;
-
- $this->previousData = array();
- $this->linkedItemsLoaded = false;
-
- $this->changed = array();
- $props = array(
- 'name',
- 'type',
- 'dateAdded',
- 'dateModified',
- 'linkedItems'
- );
- foreach ($props as $prop) {
- $this->changed[$prop] = false;
- }
- }
-
-
- public function __get($field) {
- if (($this->id || $this->key) && !$this->loaded) {
- $this->load(true);
- }
-
- if (!property_exists('Zotero_Tag', $field)) {
- throw new Exception("Zotero_Tag property '$field' doesn't exist");
- }
-
- return $this->$field;
- }
-
-
- public function __set($field, $value) {
- switch ($field) {
- case 'id':
- case 'libraryID':
- case 'key':
- if ($this->loaded) {
- throw new Exception("Cannot set $field after tag is already loaded");
- }
- $this->checkValue($field, $value);
- $this->$field = $value;
- return;
- }
-
- if ($this->id || $this->key) {
- if (!$this->loaded) {
- $this->load(true);
- }
- }
- else {
- $this->loaded = true;
- }
-
- $this->checkValue($field, $value);
-
- if ($this->$field != $value) {
- $this->prepFieldChange($field);
- $this->$field = $value;
- }
- }
-
-
- /**
- * Check if tag exists in the database
- *
- * @return bool TRUE if the item exists, FALSE if not
- */
- public function exists() {
- if (!$this->id) {
- trigger_error('$this->id not set');
- }
-
- $sql = "SELECT COUNT(*) FROM tags WHERE tagID=?";
- return !!Zotero_DB::valueQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
- }
-
-
- public function addItem($key) {
- $current = $this->getLinkedItems(true);
- if (in_array($key, $current)) {
- Z_Core::debug("Item $key already has tag {$this->libraryID}/{$this->key}");
- return false;
- }
-
- $this->prepFieldChange('linkedItems');
- $this->linkedItems[] = $key;
- return true;
- }
-
-
- public function removeItem($key) {
- $current = $this->getLinkedItems(true);
- $index = array_search($key, $current);
-
- if ($index === false) {
- Z_Core::debug("Item {$this->libraryID}/$key doesn't have tag {$this->key}");
- return false;
- }
-
- $this->prepFieldChange('linkedItems');
- array_splice($this->linkedItems, $index, 1);
- return true;
- }
-
-
- public function hasChanged() {
- // Exclude 'dateModified' from test
- $changed = $this->changed;
- if (!empty($changed['dateModified'])) {
- unset($changed['dateModified']);
- }
- return in_array(true, array_values($changed));
- }
-
-
- public function save($userID=false, $full=false) {
- if (!$this->libraryID) {
- trigger_error("Library ID must be set before saving", E_USER_ERROR);
- }
-
- Zotero_Tags::editCheck($this, $userID);
-
- if (!$this->hasChanged()) {
- Z_Core::debug("Tag $this->id has not changed");
- return false;
- }
-
- $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
-
- Zotero_DB::beginTransaction();
-
- try {
- $tagID = $this->id ? $this->id : Zotero_ID::get('tags');
- $isNew = !$this->id;
-
- Z_Core::debug("Saving tag $tagID");
-
- $key = $this->key ? $this->key : Zotero_ID::getKey();
- $timestamp = Zotero_DB::getTransactionTimestamp();
- $dateAdded = $this->dateAdded ? $this->dateAdded : $timestamp;
- $dateModified = $this->dateModified ? $this->dateModified : $timestamp;
- $version = ($this->changed['name'] || $this->changed['type'])
- ? Zotero_Libraries::getUpdatedVersion($this->libraryID)
- : $this->version;
-
- $fields = "name=?, `type`=?, dateAdded=?, dateModified=?,
- libraryID=?, `key`=?, serverDateModified=?, version=?";
- $params = array(
- $this->name,
- $this->type ? $this->type : 0,
- $dateAdded,
- $dateModified,
- $this->libraryID,
- $key,
- $timestamp,
- $version
- );
-
- try {
- if ($isNew) {
- $sql = "INSERT INTO tags SET tagID=?, $fields";
- $stmt = Zotero_DB::getStatement($sql, true, $shardID);
- Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params));
-
- // Remove from delete log if it's there
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=?
- AND objectType='tag' AND `key`=?";
- Zotero_DB::query(
- $sql, array($this->libraryID, $key), $shardID
- );
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=?
- AND objectType='tagName' AND `key`=?";
- Zotero_DB::query(
- $sql, array($this->libraryID, $this->name), $shardID
- );
- }
- else {
- $sql = "UPDATE tags SET $fields WHERE tagID=?";
- $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
- Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID)));
- }
- }
- catch (Exception $e) {
- // If an incoming tag is the same as an existing tag, but with a different key,
- // then delete the old tag and add its linked items to the new tag
- if (preg_match("/Duplicate entry .+ for key 'uniqueTags'/", $e->getMessage())) {
- // GET existing tag
- $existing = Zotero_Tags::getIDs($this->libraryID, $this->name);
- if (!$existing) {
- throw new Exception("Existing tag not found");
- }
- foreach ($existing as $id) {
- $tag = Zotero_Tags::get($this->libraryID, $id, true);
- if ($tag->__get('type') == $this->type) {
- $linked = $tag->getLinkedItems(true);
- Zotero_Tags::delete($this->libraryID, $tag->key);
- break;
- }
- }
-
- // Save again
- if ($isNew) {
- $sql = "INSERT INTO tags SET tagID=?, $fields";
- $stmt = Zotero_DB::getStatement($sql, true, $shardID);
- Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params));
-
- // Remove from delete log if it's there
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=?
- AND objectType='tag' AND `key`=?";
- Zotero_DB::query(
- $sql, array($this->libraryID, $key), $shardID
- );
- $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=?
- AND objectType='tagName' AND `key`=?";
- Zotero_DB::query(
- $sql, array($this->libraryID, $this->name), $shardID
- );
-
- }
- else {
- $sql = "UPDATE tags SET $fields WHERE tagID=?";
- $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
- Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID)));
- }
-
- $new = array_unique(array_merge($linked, $this->getLinkedItems(true)));
- $this->setLinkedItems($new);
- }
- else {
- throw $e;
- }
- }
-
- // Linked items
- if ($full || $this->changed['linkedItems']) {
- $removeKeys = array();
- $currentKeys = $this->getLinkedItems(true);
-
- if ($full) {
- $sql = "SELECT `key` FROM itemTags JOIN items "
- . "USING (itemID) WHERE tagID=?";
- $stmt = Zotero_DB::getStatement($sql, true, $shardID);
- $dbKeys = Zotero_DB::columnQueryFromStatement($stmt, $tagID);
- if ($dbKeys) {
- $removeKeys = array_diff($dbKeys, $currentKeys);
- $newKeys = array_diff($currentKeys, $dbKeys);
- }
- else {
- $newKeys = $currentKeys;
- }
- }
- else {
- if (!empty($this->previousData['linkedItems'])) {
- $removeKeys = array_diff(
- $this->previousData['linkedItems'], $currentKeys
- );
- $newKeys = array_diff(
- $currentKeys, $this->previousData['linkedItems']
- );
- }
- else {
- $newKeys = $currentKeys;
- }
- }
-
- if ($removeKeys) {
- $sql = "DELETE itemTags FROM itemTags JOIN items USING (itemID) "
- . "WHERE tagID=? AND items.key IN ("
- . implode(', ', array_fill(0, sizeOf($removeKeys), '?'))
- . ")";
- Zotero_DB::query(
- $sql,
- array_merge(array($this->id), $removeKeys),
- $shardID
- );
- }
-
- if ($newKeys) {
- $sql = "INSERT INTO itemTags (tagID, itemID) "
- . "SELECT ?, itemID FROM items "
- . "WHERE libraryID=? AND `key` IN ("
- . implode(', ', array_fill(0, sizeOf($newKeys), '?'))
- . ")";
- Zotero_DB::query(
- $sql,
- array_merge(array($tagID, $this->libraryID), $newKeys),
- $shardID
- );
- }
-
- //Zotero.Notifier.trigger('add', 'collection-item', $this->id . '-' . $itemID);
- }
-
- Zotero_DB::commit();
-
- Zotero_Tags::cachePrimaryData(
- array(
- 'id' => $tagID,
- 'libraryID' => $this->libraryID,
- 'key' => $key,
- 'name' => $this->name,
- 'type' => $this->type ? $this->type : 0,
- 'dateAdded' => $dateAdded,
- 'dateModified' => $dateModified,
- 'version' => $version
- )
- );
- }
- catch (Exception $e) {
- Zotero_DB::rollback();
- throw ($e);
- }
-
- // If successful, set values in object
- if (!$this->id) {
- $this->id = $tagID;
- }
- if (!$this->key) {
- $this->key = $key;
- }
-
- $this->init();
-
- if ($isNew) {
- Zotero_Tags::cache($this);
- }
-
- return $this->id;
- }
-
-
- public function getLinkedItems($asKeys=false) {
- if (!$this->linkedItemsLoaded) {
- $this->loadLinkedItems();
- }
-
- if ($asKeys) {
- return $this->linkedItems;
- }
-
- return array_map(function ($key) use ($libraryID) {
- return Zotero_Items::getByLibraryAndKey($this->libraryID, $key);
- }, $this->linkedItems);
- }
-
-
- public function setLinkedItems($newKeys) {
- if (!$this->linkedItemsLoaded) {
- $this->loadLinkedItems();
- }
-
- if (!is_array($newKeys)) {
- throw new Exception('$newKeys must be an array');
- }
-
- $oldKeys = $this->getLinkedItems(true);
-
- if (!$newKeys && !$oldKeys) {
- Z_Core::debug("No linked items added", 4);
- return false;
- }
-
- $addKeys = array_diff($newKeys, $oldKeys);
- $removeKeys = array_diff($oldKeys, $newKeys);
-
- // Make sure all new keys exist
- foreach ($addKeys as $key) {
- if (!Zotero_Items::existsByLibraryAndKey($this->libraryID, $key)) {
- // Return a specific error for a wrong-library tag issue
- // that I can't reproduce
- throw new Exception("Linked item $key of tag "
- . "{$this->libraryID}/{$this->key} not found",
- Z_ERROR_TAG_LINKED_ITEM_NOT_FOUND);
- }
- }
-
- if ($addKeys || $removeKeys) {
- $this->prepFieldChange('linkedItems');
- }
- else {
- Z_Core::debug('Linked items not changed', 4);
- return false;
- }
-
- $this->linkedItems = $newKeys;
- return true;
- }
-
-
- public function serialize() {
- $obj = array(
- 'primary' => array(
- 'tagID' => $this->id,
- 'dateAdded' => $this->dateAdded,
- 'dateModified' => $this->dateModified,
- 'key' => $this->key
- ),
- 'name' => $this->name,
- 'type' => $this->type,
- 'linkedItems' => $this->getLinkedItems(true),
- );
-
- return $obj;
- }
-
-
- /**
- * Converts a Zotero_Tag object to a SimpleXMLElement item
- *
- * @param object $item Zotero_Tag object
- * @return SimpleXMLElement Tag data as SimpleXML element
- */
- public function toXML($syncMode=false) {
- if (!$this->loaded) {
- $this->load();
- }
-
- $xml = new SimpleXMLElement(' ');
-
- $xml['libraryID'] = $this->libraryID;
- $xml['key'] = $this->key;
- $name = $this->name;
- if (trim($name) === "") {
- error_log("Empty tag " . $this->libraryID . "/" . $this->key);
- $name = json_decode('"\uFFFD"');
- }
- $xml['name'] = $name;
- $xml['dateAdded'] = $this->dateAdded;
- $xml['dateModified'] = $this->dateModified;
- if ($this->type) {
- $xml['type'] = $this->type;
- }
-
- if ($syncMode) {
- $itemKeys = $this->getLinkedItems(true);
- if ($itemKeys) {
- $xml->items = implode(" ", $itemKeys);
- }
- }
-
- return $xml;
- }
-
-
- public function toResponseJSON() {
- if (!$this->loaded) {
- $this->load();
- }
-
- $json = [
- 'tag' => $this->name
- ];
-
- // 'links'
- $json['links'] = [
- 'self' => [
- 'href' => Zotero_API::getTagURI($this),
- 'type' => 'application/json'
- ],
- 'alternate' => [
- 'href' => Zotero_URI::getTagURI($this, true),
- 'type' => 'text/html'
- ]
- ];
-
- // 'library'
- // Don't bother with library for tags
- //$json['library'] = Zotero_Libraries::toJSON($this->libraryID);
-
- // 'meta'
- $json['meta'] = [
- 'type' => $this->type,
- 'numItems' => isset($fixedValues['numItems'])
- ? $fixedValues['numItems']
- : sizeOf($this->getLinkedItems(true))
- ];
-
- return $json;
- }
-
-
- public function toJSON() {
- if (!$this->loaded) {
- $this->load();
- }
-
- $arr['tag'] = $this->name;
- $arr['type'] = $this->type;
-
- return $arr;
- }
-
-
- /**
- * Converts a Zotero_Tag object to a SimpleXMLElement Atom object
- *
- * @return SimpleXMLElement Tag data as SimpleXML element
- */
- public function toAtom($queryParams, $fixedValues=null) {
- if (!empty($queryParams['content'])) {
- $content = $queryParams['content'];
- }
- else {
- $content = array('none');
- }
- // TEMP: multi-format support
- $content = $content[0];
-
- $xml = new SimpleXMLElement(
- ''
- . ' '
- );
-
- $xml->title = $this->name;
-
- $author = $xml->addChild('author');
- $author->name = Zotero_Libraries::getName($this->libraryID);
- $author->uri = Zotero_URI::getLibraryURI($this->libraryID, true);
-
- $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_API::getTagURI($this);
-
- $link = $xml->addChild('link');
- $link['rel'] = 'alternate';
- $link['type'] = 'text/html';
- $link['href'] = Zotero_URI::getTagURI($this, true);
-
- // Count user's linked items
- if (isset($fixedValues['numItems'])) {
- $numItems = $fixedValues['numItems'];
- }
- else {
- $numItems = sizeOf($this->getLinkedItems(true));
- }
- $xml->addChild(
- 'zapi:numItems',
- $numItems,
- Zotero_Atom::$nsZoteroAPI
- );
-
- if ($content == 'html') {
- $xml->content['type'] = 'xhtml';
-
- $contentXML = new SimpleXMLElement("");
- $contentXML->addAttribute(
- "xmlns", Zotero_Atom::$nsXHTML
- );
- $fNode = dom_import_simplexml($xml->content);
- $subNode = dom_import_simplexml($contentXML);
- $importedNode = $fNode->ownerDocument->importNode($subNode, true);
- $fNode->appendChild($importedNode);
- }
- else if ($content == 'json') {
- $xml->content['type'] = 'application/json';
- $xml->content = Zotero_Utilities::formatJSON($this->toJSON());
- }
-
- return $xml;
- }
-
-
- private function load() {
- $libraryID = $this->libraryID;
- $id = $this->id;
- $key = $this->key;
-
- if (!$libraryID) {
- throw new Exception("Library ID not set");
- }
-
- if (!$id && !$key) {
- throw new Exception("ID or key not set");
- }
-
- // Cache tag data for the entire library
- if (true) {
- if ($id) {
- Z_Core::debug("Loading data for tag $this->libraryID/$this->id");
- $row = Zotero_Tags::getPrimaryDataByID($libraryID, $id);
- }
- else {
- Z_Core::debug("Loading data for tag $this->libraryID/$this->key");
- $row = Zotero_Tags::getPrimaryDataByKey($libraryID, $key);
- }
-
- $this->loaded = true;
-
- if (!$row) {
- return;
- }
-
- if ($row['libraryID'] != $libraryID) {
- throw new Exception("libraryID {$row['libraryID']} != $this->libraryID");
- }
-
- foreach ($row as $key=>$val) {
- $this->$key = $val;
- }
- }
- // Load tag row individually
- else {
- // Use cached check for existence if possible
- if ($libraryID && $key) {
- if (!Zotero_Tags::existsByLibraryAndKey($libraryID, $key)) {
- $this->loaded = true;
- return;
- }
- }
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- $sql = Zotero_Tags::getPrimaryDataSQL();
- if ($id) {
- $sql .= "tagID=?";
- $stmt = Zotero_DB::getStatement($sql, false, $shardID);
- $data = Zotero_DB::rowQueryFromStatement($stmt, $id);
- }
- else {
- $sql .= "libraryID=? AND `key`=?";
- $stmt = Zotero_DB::getStatement($sql, false, $shardID);
- $data = Zotero_DB::rowQueryFromStatement($stmt, array($libraryID, $key));
- }
-
- $this->loaded = true;
-
- if (!$data) {
- return;
- }
-
- if ($data['libraryID'] != $libraryID) {
- throw new Exception("libraryID {$data['libraryID']} != $libraryID");
- }
-
- foreach ($data as $k=>$v) {
- $this->$k = $v;
- }
- }
- }
-
-
- private function loadLinkedItems() {
- Z_Core::debug("Loading linked items for tag $this->id");
-
- if (!$this->id && !$this->key) {
- $this->linkedItemsLoaded = true;
- return;
- }
-
- if (!$this->loaded) {
- $this->load();
- }
-
- if (!$this->id) {
- $this->linkedItemsLoaded = true;
- return;
- }
-
- $sql = "SELECT `key` FROM itemTags JOIN items USING (itemID) WHERE tagID=?";
- $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
- $keys = Zotero_DB::columnQueryFromStatement($stmt, $this->id);
-
- $this->linkedItems = $keys ? $keys : array();
- $this->linkedItemsLoaded = true;
- }
-
-
- private function checkValue($field, $value) {
- if (!property_exists($this, $field)) {
- trigger_error("Invalid property '$field'", E_USER_ERROR);
- }
-
- // Data validation
- switch ($field) {
- case 'id':
- case 'libraryID':
- if (!Zotero_Utilities::isPosInt($value)) {
- $this->invalidValueError($field, $value);
- }
- break;
-
- case 'key':
- // 'I' used to exist in client
- if (!preg_match('/^[23456789ABCDEFGHIJKLMNPQRSTUVWXYZ]{8}$/', $value)) {
- $this->invalidValueError($field, $value);
- }
- break;
-
- case 'dateAdded':
- case 'dateModified':
- if (!preg_match("/^[0-9]{4}\-[0-9]{2}\-[0-9]{2} ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$/", $value)) {
- $this->invalidValueError($field, $value);
- }
- break;
-
- case 'name':
- if (mb_strlen($value) > Zotero_Tags::$maxLength) {
- throw new Exception("Tag '" . $value . "' too long", Z_ERROR_TAG_TOO_LONG);
- }
- break;
- }
- }
-
-
- private function prepFieldChange($field) {
- $this->changed[$field] = true;
-
- // Save a copy of the data before changing
- // TODO: only save previous data if tag exists
- if ($this->id && $this->exists() && !$this->previousData) {
- $this->previousData = $this->serialize();
- }
- }
-
-
- private function invalidValueError($field, $value) {
- trigger_error("Invalid '$field' value '$value'", E_USER_ERROR);
- }
-}
-?>
diff --git a/model/Tags.inc.php b/model/Tags.inc.php
deleted file mode 100644
index 1a37b663..00000000
--- a/model/Tags.inc.php
+++ /dev/null
@@ -1,326 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Tags extends Zotero_ClassicDataObjects {
- public static $maxLength = 255;
-
- protected static $ZDO_object = 'tag';
-
- protected static $primaryFields = array(
- 'id' => 'tagID',
- 'libraryID' => '',
- 'key' => '',
- 'name' => '',
- 'type' => '',
- 'dateAdded' => '',
- 'dateModified' => '',
- 'version' => ''
- );
-
- private static $tagsByID = array();
- private static $namesByHash = array();
-
- /*
- * Returns a tag and type for a given tagID
- */
- public static function get($libraryID, $tagID, $skipCheck=false) {
- if (!$libraryID) {
- throw new Exception("Library ID not provided");
- }
-
- if (!$tagID) {
- throw new Exception("Tag ID not provided");
- }
-
- if (isset(self::$tagsByID[$tagID])) {
- return self::$tagsByID[$tagID];
- }
-
- if (!$skipCheck) {
- $sql = 'SELECT COUNT(*) FROM tags WHERE tagID=?';
- $result = Zotero_DB::valueQuery($sql, $tagID, Zotero_Shards::getByLibraryID($libraryID));
- if (!$result) {
- return false;
- }
- }
-
- $tag = new Zotero_Tag;
- $tag->libraryID = $libraryID;
- $tag->id = $tagID;
-
- self::$tagsByID[$tagID] = $tag;
- return self::$tagsByID[$tagID];
- }
-
-
- /*
- * Returns tagID for this tag
- */
- public static function getID($libraryID, $name, $type, $caseInsensitive=false) {
- if (!$libraryID) {
- throw new Exception("Library ID not provided");
- }
-
- $name = trim($name);
- $type = (int) $type;
-
- // TODO: cache
-
- $sql = "SELECT tagID FROM tags WHERE ";
- if ($caseInsensitive) {
- $sql .= "LOWER(name)=?";
- $params = [strtolower($name)];
- }
- else {
- $sql .= "name=?";
- $params = [$name];
- }
- $sql .= " AND type=? AND libraryID=?";
- array_push($params, $type, $libraryID);
- $tagID = Zotero_DB::valueQuery($sql, $params, Zotero_Shards::getByLibraryID($libraryID));
-
- return $tagID;
- }
-
-
- /*
- * Returns array of all tagIDs for this tag (of all types)
- */
- public static function getIDs($libraryID, $name, $caseInsensitive=false) {
- // Default empty library
- if ($libraryID === 0) return [];
-
- $sql = "SELECT tagID FROM tags WHERE libraryID=? AND name";
- if ($caseInsensitive) {
- $sql .= " COLLATE utf8mb4_unicode_ci ";
- }
- $sql .= "=?";
- $tagIDs = Zotero_DB::columnQuery($sql, array($libraryID, $name), Zotero_Shards::getByLibraryID($libraryID));
- if (!$tagIDs) {
- return array();
- }
- return $tagIDs;
- }
-
-
- public static function search($libraryID, $params) {
- $results = array('results' => array(), 'total' => 0);
-
- // Default empty library
- if ($libraryID === 0) {
- return $results;
- }
-
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
-
- $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT tagID FROM tags "
- . "JOIN itemTags USING (tagID) WHERE libraryID=? ";
- $sqlParams = array($libraryID);
-
- // Pass a list of tagIDs, for when the initial search is done via SQL
- $tagIDs = !empty($params['tagIDs']) ? $params['tagIDs'] : array();
- // Filter for specific tags with "?tag=foo || bar"
- $tagNames = !empty($params['tag']) ? explode(' || ', $params['tag']): array();
-
- if ($tagIDs) {
- $sql .= "AND tagID IN ("
- . implode(', ', array_fill(0, sizeOf($tagIDs), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $tagIDs);
- }
-
- if ($tagNames) {
- $sql .= "AND `name` IN ("
- . implode(', ', array_fill(0, sizeOf($tagNames), '?'))
- . ") ";
- $sqlParams = array_merge($sqlParams, $tagNames);
- }
-
- if (!empty($params['q'])) {
- if (!is_array($params['q'])) {
- $params['q'] = array($params['q']);
- }
- foreach ($params['q'] as $q) {
- $sql .= "AND name LIKE ? ";
- $sqlParams[] = "%$q%";
- }
- }
-
- $tagTypeSets = Zotero_API::getSearchParamValues($params, 'tagType');
- if ($tagTypeSets) {
- $positives = array();
- $negatives = array();
-
- foreach ($tagTypeSets as $set) {
- if ($set['negation']) {
- $negatives = array_merge($negatives, $set['values']);
- }
- else {
- $positives = array_merge($positives, $set['values']);
- }
- }
-
- if ($positives) {
- $sql .= "AND type IN (" . implode(',', array_fill(0, sizeOf($positives), '?')) . ") ";
- $sqlParams = array_merge($sqlParams, $positives);
- }
-
- if ($negatives) {
- $sql .= "AND type NOT IN (" . implode(',', array_fill(0, sizeOf($negatives), '?')) . ") ";
- $sqlParams = array_merge($sqlParams, $negatives);
- }
- }
-
- if (!empty($params['since'])) {
- $sql .= "AND version > ? ";
- $sqlParams[] = $params['since'];
- }
-
- if (!empty($params['sort'])) {
- $order = $params['sort'];
- if ($order == 'title') {
- // Force a case-insensitive sort
- $sql .= "ORDER BY name COLLATE utf8mb4_unicode_ci ";
- }
- else if ($order == 'numItems') {
- $sql .= "GROUP BY tags.tagID ORDER BY COUNT(tags.tagID)";
- }
- else {
- $sql .= "ORDER BY $order ";
- }
- if (!empty($params['direction'])) {
- $sql .= " " . $params['direction'] . " ";
- }
- }
-
- if (!empty($params['limit'])) {
- $sql .= "LIMIT ?, ?";
- $sqlParams[] = $params['start'] ? $params['start'] : 0;
- $sqlParams[] = $params['limit'];
- }
-
- $ids = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
-
- $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
- if ($ids) {
- $tags = array();
- foreach ($ids as $id) {
- $tags[] = Zotero_Tags::get($libraryID, $id);
- }
- $results['results'] = $tags;
- }
-
- return $results;
- }
-
-
- public static function cache(Zotero_Tag $tag) {
- if (isset($tagsByID[$tag->id])) {
- error_log("Tag $tag->id is already cached");
- }
-
- self::$tagsByID[$tag->id] = $tag;
- }
-
-
- /*public static function getDataValuesFromXML(DOMDocument $doc) {
- $xpath = new DOMXPath($doc);
- $attr = $xpath->evaluate('//tags/tag/@name');
- $vals = array();
- foreach ($attr as $a) {
- $vals[] = $a->value;
- }
- $vals = array_unique($vals);
- return $vals;
- }*/
-
-
- public static function getLongDataValueFromXML(DOMDocument $doc) {
- $xpath = new DOMXPath($doc);
- $attr = $xpath->evaluate('//tags/tag[string-length(@name) > ' . self::$maxLength . ']/@name');
- return $attr->length ? $attr->item(0)->value : false;
- }
-
-
- /**
- * Converts a DOMElement item to a Zotero_Tag object
- *
- * @param DOMElement $xml Tag data as DOMElement
- * @param int $libraryID Library ID
- * @return Zotero_Tag Zotero tag object
- */
- public static function convertXMLToTag(DOMElement $xml, &$itemKeysToUpdate) {
- $libraryID = (int) $xml->getAttribute('libraryID');
- $tag = self::getByLibraryAndKey($libraryID, $xml->getAttribute('key'));
- if (!$tag) {
- $tag = new Zotero_Tag;
- $tag->libraryID = $libraryID;
- $tag->key = $xml->getAttribute('key');
- }
- $tag->name = $xml->getAttribute('name');
- $type = (int) $xml->getAttribute('type');
- $tag->type = $type ? $type : 0;
- $tag->dateAdded = $xml->getAttribute('dateAdded');
- $tag->dateModified = $xml->getAttribute('dateModified');
-
- $dataChanged = $tag->hasChanged();
-
- $itemKeys = $xml->getElementsByTagName('items');
- $oldKeys = $tag->getLinkedItems(true);
- if ($itemKeys->length) {
- $newKeys = explode(' ', $itemKeys->item(0)->nodeValue);
- }
- else {
- $newKeys = array();
- }
- $addKeys = array_diff($newKeys, $oldKeys);
- $removeKeys = array_diff($oldKeys, $newKeys);
-
- // If the data has changed, all old and new items need to change
- if ($dataChanged) {
- $itemKeysToUpdate = array_merge($oldKeys, $addKeys);
- }
- // Otherwise, only update items that are being added or removed
- else {
- $itemKeysToUpdate = array_merge($addKeys, $removeKeys);
- }
-
- $tag->setLinkedItems($newKeys);
- return $tag;
- }
-
-
- /**
- * Converts a Zotero_Tag object to a SimpleXMLElement item
- *
- * @param object $item Zotero_Tag object
- * @return SimpleXMLElement Tag data as SimpleXML element
- */
- public static function convertTagToXML(Zotero_Tag $tag, $syncMode=false) {
- return $tag->toXML($syncMode);
- }
-}
-?>
diff --git a/model/ToolkitVersionComparator.inc.php b/model/ToolkitVersionComparator.inc.php
deleted file mode 100644
index 63663cf1..00000000
--- a/model/ToolkitVersionComparator.inc.php
+++ /dev/null
@@ -1,294 +0,0 @@
-
-/* ***** BEGIN LICENSE BLOCK *****
-* Version: MPL 1.1/GPL 2.0/LGPL 2.1
-*
-* The contents of this file are subject to the Mozilla Public License Version
-* 1.1 (the "License"); you may not use this file except in compliance with
-* the License. You may obtain a copy of the License at
-* http://www.mozilla.org/MPL/
-*
-* Software distributed under the License is distributed on an "AS IS" basis,
-* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-* for the specific language governing rights and limitations under the
-* License.
-*
-* The Original Code is Java XPCOM Bindings.
-*
-* The Initial Developer of the Original Code is
-* IBM Corporation.
-* Portions created by the Initial Developer are Copyright (C) 2005
-* IBM Corporation. All Rights Reserved.
-*
-* Contributor(s):
-* Javier Pedemonte (jhpedemonte@gmail.com)
-*
-* Ported to PHP by Dan Stillman (dstillman _at_ zotero.org)
-*
-* Alternatively, the contents of this file may be used under the terms of
-* either the GNU General Public License Version 2 or later (the "GPL"), or
-* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-* in which case the provisions of the GPL or the LGPL are applicable instead
-* of those above. If you wish to allow use of your version of this file only
-* under the terms of either the GPL or the LGPL, and not to allow others to
-* use your version of this file under the terms of the MPL, indicate your
-* decision by deleting the provisions above and replace them with the notice
-* and other provisions required by the GPL or the LGPL. If you do not delete
-* the provisions above, a recipient may use your version of this file under
-* the terms of any one of the MPL, the GPL or the LGPL.
-*
-* ***** END LICENSE BLOCK ***** */
-
-
-//
-// Unit tests
-//
-// (compare results to version order in specifications below)
-//
-/*
-$all = array('1.0pre1', '1.0pre2', '1.0', '1.0.0', '1.0.0.0', '1.1pre', '1.1pre0', '1.0+',
- '1.1pre1a', '1.1pre1', '1.1pre10a', '1.1pre10');
-
-for ($i=0; $i0) {
- echo $a . ' is greater than ' . $b;
- }
- echo "\n";
- }
- echo "\n\n";
-}
-*/
-
-
-/**
- * Implements Mozilla Toolkit's nsIVersionComparator
- *
- * Version strings are dot-separated sequences of version-parts.
- *
- * A version-part consists of up to four parts, all of which are optional:
- *
- * A version-part may also consist of a single asterisk "*" which indicates
- * "infinity".
- *
- * Numbers are base-10, and are zero if left out.
- * Strings are compared bytewise.
- *
- * For additional backwards compatibility, if "string-b" is "+" then
- * "number-a" is incremented by 1 and "string-b" becomes "pre".
- *
- * 1.0pre1
- * < 1.0pre2
- * < 1.0 == 1.0.0 == 1.0.0.0
- * < 1.1pre == 1.1pre0 == 1.0+
- * < 1.1pre1a
- * < 1.1pre1
- * < 1.1pre10a
- * < 1.1pre10
- *
- * Although not required by this interface, it is recommended that
- * numbers remain within the limits of a signed char, i.e. -127 to 128.
- */
-class ToolkitVersionComparator {
- public static function compare($a, $b) {
- do {
- $va = new ToolkitVersionPart();
- $vb = new ToolkitVersionPart();
- $a = self::parseVersionPart($a, $va);
- $b = self::parseVersionPart($b, $vb);
-
- $result = self::compareVersionPart($va, $vb);
-
- if ($result != 0){
- break;
- }
- }
- while ($a != null || $b != null);
-
- return $result;
- }
-
-
- private static function parseVersionPart($aVersion, ToolkitVersionPart $result) {
- if ($aVersion === null || strlen($aVersion) == 0) {
- return $aVersion;
- }
-
- $tok = explode(".", trim($aVersion));
- $part = $tok[0];
-
- if ($part == "*") {
- $result->numA = 9999999999;
- $result->strB = "";
- }
- else {
- $vertok = new ToolkitVersionPartTokenizer($part);
- $next = $vertok->nextToken();
- if (is_numeric($next)){
- $result->numA = $next;
- }
- else {
- $result->numA = 0;
- }
-
- if ($vertok->hasMoreElements()) {
- $str = $vertok->nextToken();
- // if part is of type "+"
- if ($str[0] == '+') {
- $result->numA++;
- $result->strB = "pre";
- }
- else {
- // else if part is of type "..."
- $result->strB = $str;
-
- if ($vertok->hasMoreTokens()) {
- $next = $vertok->nextToken();
- if (is_numeric($next)){
- $result->numC = $next;
- }
- else {
- $result->numC = 0;
- }
- if ($vertok->hasMoreTokens()) {
- $result->extraD = $vertok->getRemainder();
- }
- }
- }
- }
- }
-
- if (sizeOf($tok)>1) {
- // return everything after "."
- return substr($aVersion, strlen($part) + 1);
- }
- return null;
- }
-
-
- private static function compareVersionPart(ToolkitVersionPart $va, ToolkitVersionPart $vb) {
- $res = self::compareInt($va->numA, $vb->numA);
- if ($res != 0) {
- return $res;
- }
-
- $res = self::compareString($va->strB, $vb->strB);
- if ($res != 0) {
- return $res;
- }
-
- $res = self::compareInt($va->numC, $vb->numC);
- if ($res != 0) {
- return $res;
- }
-
- return self::compareString($va->extraD, $vb->extraD);
- }
-
-
- private static function compareInt($n1, $n2) {
- return $n1 - $n2;
- }
-
-
- private static function compareString($str1, $str2) {
- // any string is *before* no string
- if ($str1 === null) {
- return ($str2 !== null) ? 1 : 0;
- }
-
- if ($str2 === null) {
- return -1;
- }
-
- return strcmp($str1, $str2);
- }
-
-
-}
-
-
-class ToolkitVersionPart {
- public $numA = 0;
- public $strB = null;
- public $numC = 0;
- public $extraD = null;
-}
-
-
-/**
- * Specialized tokenizer for Mozilla version strings. A token can
- * consist of one of the four sections of a version string:
- *
- */
-class ToolkitVersionPartTokenizer {
- private $part = '';
-
-
- public function __construct($aPart) {
- $this->part = $aPart;
- }
-
-
- public function hasMoreElements() {
- return strlen($this->part) != 0;
- }
-
-
- public function hasMoreTokens() {
- return strlen($this->part) != 0;
- }
-
-
- public function nextElement() {
- if (preg_match('/^[\+\-]?[0-9].*/', $this->part)) {
- // if string starts with a number...
- $index = 0;
- if ($this->part[0] == '+' || $this->part[0] == '-') {
- $index = 1;
- }
- while (($index < strlen($this->part)) && is_numeric($this->part[$index])) {
- $index++;
- }
- $numPart = substr($this->part, 0, $index);
- $this->part = substr($this->part, $index);
- return $numPart;
- }
- else {
- // ... or if this is the non-numeric part of version string
- $index = 0;
- while (($index < strlen($this->part)) && !is_numeric($this->part[$index])) {
- $index++;
- }
- $alphaPart = substr($this->part, 0, $index);
- $this->part = substr($this->part, $index);
- return $alphaPart;
- }
- }
-
-
- public function nextToken() {
- return $this->nextElement();
- }
-
-
- /**
- * Returns what remains of the original string, without tokenization. This
- * method is useful for getting the ;
- * section of a version string.
- *
- * @return remaining version string
- */
- public function getRemainder() {
- return $this->part;
- }
-}
-?>
diff --git a/model/Translate.inc.php b/model/Translate.inc.php
deleted file mode 100644
index 22de9dac..00000000
--- a/model/Translate.inc.php
+++ /dev/null
@@ -1,232 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Translate {
- public static $exportFormats = array(
- 'bibtex',
- 'biblatex',
- 'bookmarks',
- 'coins',
- 'csljson',
- 'rdf_bibliontology',
- 'rdf_dc',
- 'rdf_zotero',
- 'mods',
- 'refer',
- 'ris',
- 'tei',
- 'wikipedia'
- );
-
- /**
- * @param array[] $items Array of item JSON objects
- * @param string $requestParams Request parameters
- */
- public static function doExport($items, $requestParams) {
- $format = $requestParams['format'];
-
- if (!in_array($format, self::$exportFormats)) {
- throw new Exception("Invalid export format '$format'");
- }
-
- $jsonItems = array();
- foreach ($items as $item) {
- $arr = $item->toJSON(true, $requestParams);
- $arr['uri'] = Zotero_URI::getItemURI($item);
- $jsonItems[] = $arr;
- }
-
- if (!$jsonItems) {
- return array(
- 'body' => "",
- // Stripping the Content-Type header (header_remove, "Content-Type:")
- // in the API controller doesn't seem to be working, so send
- // text/plain instead
- 'mimeType' => "text/plain"
- );
- }
-
- $json = json_encode($jsonItems);
-
- $servers = Z_CONFIG::$TRANSLATION_SERVERS;
-
- // Try servers in a random order
- shuffle($servers);
-
- foreach ($servers as $server) {
- $url = "http://$server/export?format=$format";
-
- $start = microtime(true);
-
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array("Expect:", "Content-Type: application/json"));
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 4);
- curl_setopt($ch, CURLOPT_HEADER, 0); // do not return HTTP headers
- curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
- $response = curl_exec($ch);
-
- $time = microtime(true) - $start;
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $mimeType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
-
- if ($code != 200) {
- $response = null;
- Z_Core::logError("HTTP $code from translate server $server exporting items");
- Z_Core::logError($response);
- continue;
- }
-
- if (!$response) {
- $response = "";
- }
-
- break;
- }
-
- if ($response === null) {
- StatsD::increment("translate.export.$format.error");
- throw new Exception("Error exporting items");
- }
-
- $export = array(
- 'body' => $response,
- 'mimeType' => $mimeType
- );
-
- StatsD::increment("translate.export.$format.success");
- return $export;
- }
-
-
- public static function doWeb($url, $sessionKey, $items=false) {
- if (!$sessionKey) {
- throw new Exception("Session key not provided");
- }
-
- $servers = Z_CONFIG::$TRANSLATION_SERVERS;
-
- // Try servers in a random order
- shuffle($servers);
-
- $cacheKey = 'sessionTranslationServer_' . $sessionKey;
-
- $json = [
- "url" => $url,
- "sessionid" => $sessionKey
- ];
-
- if ($items) {
- $json['items'] = $items;
-
- // Send session requests to the same node
- if ($server = Z_Core::$MC->get($cacheKey)) {
- $servers = [$server];
- }
- else {
- error_log("WARNING: Server not found for translation session");
- }
- }
-
- $json = json_encode($json);
-
-
- foreach ($servers as $server) {
- $serverURL = "http://$server/web";
-
- $start = microtime(true);
-
- $ch = curl_init($serverURL);
- curl_setopt($ch, CURLOPT_POST, 1);
- curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array("Expect:", "Content-Type: application/json"));
- curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);
- curl_setopt($ch, CURLOPT_TIMEOUT, 4);
- curl_setopt($ch, CURLOPT_HEADER, 0); // do not return HTTP headers
- curl_setopt($ch, CURLOPT_RETURNTRANSFER , 1);
- $response = curl_exec($ch);
-
- $time = microtime(true) - $start;
-
- $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
- $mimeType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
-
- if ($code != 200 && $code != 300) {
- // For explicit errors, trust translation server and bail with response code
- if ($code == 500 && strpos($response, "An error occurred during translation") !== false) {
- error_log("Error translating $url");
- return 500;
- }
- else if ($code == 501) {
- error_log("No translators found for $url");
- return 501;
- }
-
- // If unknown error, log and try another server
- $response = null;
- Z_Core::logError("HTTP $code from translate server $server translating URL");
- Z_Core::logError($response);
- continue;
- }
-
- if (!$response) {
- $response = "";
- }
-
- // Remember translation-server node for item selection
- if ($code == 300) {
- Z_Core::$MC->set($cacheKey, $server, 600);
- }
- break;
- }
-
- if ($response === null) {
- throw new Exception("Error from translation server");
- }
-
- $response = json_decode($response);
-
- $obj = new stdClass;
- // Multiple choices
- if ($code == 300) {
- $obj->select = $response;
- }
- // Saved items
- else {
- $obj->items = $response;
- }
-
- return $obj;
- }
-
-
- public static function isExportFormat($format) {
- return in_array($format, self::$exportFormats);
- }
-}
diff --git a/model/URI.inc.php b/model/URI.inc.php
deleted file mode 100644
index ec9759fd..00000000
--- a/model/URI.inc.php
+++ /dev/null
@@ -1,186 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_URI {
- public static function getBaseURI() {
- return Z_CONFIG::$BASE_URI;
- }
-
- public static function getBaseWWWURI() {
- return Z_CONFIG::$WWW_BASE_URI;
- }
-
- 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);
-
- // TEMP
- 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'");
- }
- }
-
- public static function getUserURI($userID, $www=false, $useSlug=false) {
- if ($www) {
- $username = Zotero_Users::getUsername($userID);
- return self::getBaseWWWURI() . Zotero_Utilities::slugify($username);
- }
- if ($useSlug) {
- $username = Zotero_Users::getUsername($userID);
- return self::getBaseURI() . Zotero_Utilities::slugify($username);
- }
- return self::getBaseURI() . "users/$userID";
- }
-
- public static function getItemURI(Zotero_Item $item, $www=false, $useSlug=false) {
- if (!$item->libraryID) {
- throw new Exception("Can't get URI for unsaved item");
- }
- return self::getLibraryURI($item->libraryID, $www, $useSlug) . "/items/$item->key";
- }
-
- public static function getGroupURI(Zotero_Group $group, $www=false, $useSlug=false) {
- if ($www) {
- $slug = $group->slug;
- if (!$slug) {
- $slug = $group->id;
- }
- return self::getBaseWWWURI() . "groups/$slug";
- }
- if ($useSlug) {
- $id = $group->slug;
- if ($id === null) {
- $id = $group->id;
- }
- }
- else {
- $id = $group->id;
- }
- return self::getBaseURI() . "groups/" . $id;
- }
-
- public static function getGroupUserURI(Zotero_Group $group, $userID) {
- return self::getGroupURI($group) . "/users/$userID";
- }
-
- public static function getGroupItemURI(Zotero_Group $group, Zotero_Item $item) {
- return self::getGroupURI($group) . "/items/$item->key";
- }
-
- public static function getCollectionURI(Zotero_Collection $collection, $www=false) {
- return self::getLibraryURI($collection->libraryID, true) . "/collections/$collection->key";
- }
-
- public static function getCreatorURI(Zotero_Creator $creator) {
- return self::getLibraryURI($creator->libraryID) . "/creators/$creator->key";
- }
-
- public static function getSearchURI(Zotero_Search $search) {
- return self::getLibraryURI($search->libraryID) . "/searches/$search->key";
- }
-
- public static function getTagURI(Zotero_Tag $tag) {
- return self::getLibraryURI($tag->libraryID) . "/tags/" . urlencode($tag->name);
- }
-
- public static function getURIItem($itemURI) {
- return self::getURIObject($itemURI, 'item');
- }
-
-
- public static function getURICollection($collectionURI) {
- return self::getURIObject($collectionURI, 'collection');
- }
-
-
- public static function getURILibrary($libraryURI) {
- return self::getURIObject($libraryURI, "library");
- }
-
-
- private static function getURIObject($objectURI, $type) {
- $Types = ucwords($type) . 's';
- $types = strtolower($Types);
-
- $libraryType = null;
-
- $baseURI = self::getBaseURI();
-
- // If not found, try global URI
- if (strpos($objectURI, $baseURI) !== 0) {
- throw new Exception("Invalid base URI '$objectURI'");
- }
- $objectURI = substr($objectURI, strlen($baseURI));
- $typeRE = "/^(users|groups)\/([0-9]+)(?:\/|$)/";
- if (!preg_match($typeRE, $objectURI, $matches)) {
- throw new Exception("Invalid library URI '$objectURI'");
- }
- $libraryType = substr($matches[1], 0, -1);
- $id = $matches[2];
- $objectURI = preg_replace($typeRE, '', $objectURI);
-
- if ($libraryType == 'user') {
- if (!Zotero_Users::exists($id)) {
- return false;
- }
- $libraryID = Zotero_Users::getLibraryIDFromUserID($id);
- }
- else if ($libraryType == 'group') {
- if (!Zotero_Groups::get($id)) {
- return false;
- }
- $libraryID = Zotero_Groups::getLibraryIDFromGroupID($id);
- }
- else {
- throw new Exception("Invalid library type $libraryType");
- }
-
- if ($type === 'library') {
- return $libraryID;
- }
- else {
- // TODO: objectID-based URI?
- if (!preg_match('/' . $types . "\/([A-Z0-9]{8})/", $objectURI, $matches)) {
- throw new Exception("Invalid object URI '$objectURI'");
- }
- $objectKey = $matches[1];
- return call_user_func(array("Zotero_$Types", "getByLibraryAndKey"), $libraryID, $objectKey);
- }
- }
-}
-?>
diff --git a/model/URL.inc.php b/model/URL.inc.php
deleted file mode 100644
index 9619f241..00000000
--- a/model/URL.inc.php
+++ /dev/null
@@ -1,54 +0,0 @@
-
-/*
- Add license block if adding additional code
-*/
-
-class Zotero_URL {
- /**
- * Handle multiple identical parameters in the CGI-standard way instead of
- * PHP's foo[]=bar way
- *
- * By Evan K on http://us.php.net/manual/en/function.parse-str.php
- */
- public static function proper_parse_str($str) {
- if (!$str) {
- return array();
- }
- $arr = array();
-
- $pairs = explode('&', $str);
-
- foreach ($pairs as $i) {
- // Skip if no equals sign
- if (strpos($i, '=') === false) {
- continue;
- }
-
- list($name, $value) = explode('=', $i, 2);
-
- // Skip if empty value
- if (!$value && $value !== '0') {
- continue;
- }
-
- // Added by Dan S.
- $value = urldecode($value);
-
- // if name already exists
- if (isset($arr[$name])) {
- // stick multiple values into an array
- if (is_array($arr[$name])) {
- $arr[$name][] = $value;
- }
- else {
- $arr[$name] = array($arr[$name], $value);
- }
- }
- // otherwise, simply stick it in a scalar
- else {
- $arr[$name] = $value;
- }
- }
- return $arr;
- }
-}
diff --git a/model/Users.inc.php b/model/Users.inc.php
deleted file mode 100644
index 2c1c863e..00000000
--- a/model/Users.inc.php
+++ /dev/null
@@ -1,607 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Users {
- public static $userXMLHash = array();
-
- private static $usernamesByID = [];
- private static $realNamesByID = [];
- private static $userLibraryIDs = [];
- private static $libraryUserIDs = [];
-
-
- /**
- * Get the id of the library of the given type associated with the given user
- *
- * @param int $userID
- * @param string [$libraryType='user']
- * @throws Exception with code Z_ERROR_USER_NOT_FOUND if user library missing
- * @return int|false Library ID, or false if library not found (except user library,
- * which throws)
- */
- public static function getLibraryIDFromUserID($userID, $libraryType='user') {
- if (isset(self::$userLibraryIDs[$libraryType][$userID])) {
- return self::$userLibraryIDs[$libraryType][$userID];
- }
- $cacheKey = 'user' . ucwords($libraryType) . 'LibraryID_' . $userID;
- $libraryID = Z_Core::$MC->get($cacheKey);
- if ($libraryID) {
- self::$userLibraryIDs[$libraryType][$libraryID] = $libraryID;
- return $libraryID;
- }
- switch ($libraryType) {
- case 'user':
- $sql = "SELECT libraryID FROM users WHERE userID=?";
- break;
-
- case 'publications':
- $sql = "SELECT libraryID FROM userPublications WHERE userID=?";
- break;
-
- case 'group':
- throw new Exception("Can't get single group libraryID from userID");
- }
-
- $libraryID = Zotero_DB::valueQuery($sql, $userID);
- if (!$libraryID) {
- if ($libraryType == 'publications') {
- return false;
- }
- throw new Exception(ucwords($libraryType) . " library not found for user $userID",
- Z_ERROR_USER_NOT_FOUND);
- }
- self::$userLibraryIDs[$libraryType][$userID] = $libraryID;
- Z_Core::$MC->set($cacheKey, $libraryID);
- return $libraryID;
- }
-
-
- public static function getUserIDFromLibraryID($libraryID, $libraryType=null) {
- if (isset(self::$libraryUserIDs[$libraryID])) {
- return self::$libraryUserIDs[$libraryID];
- }
- $cacheKey = 'libraryUserID_' . $libraryID;
- $userID = Z_Core::$MC->get($cacheKey);
- if ($userID) {
- self::$libraryUserIDs[$libraryID] = $userID;
- return $userID;
- }
-
- if ($libraryType == null) {
- $libraryType = Zotero_Libraries::getType($libraryID);
- }
-
- switch ($libraryType) {
- case 'user':
- $sql = "SELECT userID FROM users WHERE libraryID=?";
- break;
-
- case 'publications':
- $sql = "SELECT userID FROM userPublications WHERE libraryID=?";
- break;
-
- case 'group':
- $sql = "SELECT userID FROM groupUsers JOIN groups USING (groupID) "
- . "WHERE libraryID=? AND role='owner'";
- break;
- }
-
- $userID = Zotero_DB::valueQuery($sql, $libraryID);
- if (!$userID) {
- if (!Zotero_Libraries::exists($libraryID)) {
- throw new Exception("Library $libraryID does not exist");
- }
- // Wrong library type passed
- error_log("Wrong library type passed for library $libraryID "
- . "in Zotero_Users::getUserIDFromLibraryID()");
- return self::getUserIDFromLibraryID($libraryID);
- }
- self::$libraryUserIDs[$libraryID] = $userID;
- Z_Core::$MC->set($cacheKey, $userID);
- return $userID;
- }
-
-
- /**
- * Warning: This method may lie or return false..
- */
- public static function getUserIDFromUsername($username) {
- $sql = "SELECT userID FROM users WHERE username=?";
- return Zotero_DB::valueQuery($sql, $username);
- }
-
-
- public static function getUserIDFromSessionID($sessionID) {
- $sql = "SELECT userID FROM sessions WHERE id=?
- AND UNIX_TIMESTAMP() < modified + lifetime";
- try {
- $userID = Zotero_WWW_DB_2::valueQuery($sql, $sessionID);
- Zotero_WWW_DB_2::close();
- }
- catch (Exception $e) {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $userID = Zotero_WWW_DB_1::valueQuery($sql, $sessionID);
- Zotero_WWW_DB_1::close();
- }
-
- return $userID;
- }
-
-
- public static function getUsername($userID, $skipAutoAdd=false) {
- if (!empty(self::$usernamesByID[$userID])) {
- return self::$usernamesByID[$userID];
- }
-
- $cacheKey = "usernameByID_" . $userID;
- $username = Z_Core::$MC->get($cacheKey);
- if ($username) {
- self::$usernamesByID[$userID] = $username;
- return $username;
- }
-
- if (!$skipAutoAdd) {
- if (!self::exists($userID)) {
- self::addFromWWW($userID);
- }
- else {
- self::updateFromWWW($userID);
- }
- }
-
- $sql = "SELECT username FROM users WHERE userID=?";
- $username = Zotero_DB::valueQuery($sql, $userID);
- if (!$username) {
- throw new Exception("Username for userID $userID not found", Z_ERROR_USER_NOT_FOUND);
- }
-
- self::$usernamesByID[$userID] = $username;
- Z_Core::$MC->set($cacheKey, $username, 43200);
-
- return $username;
- }
-
-
- public static function getRealName($userID) {
- if (!empty(self::$realNamesByID[$userID])) {
- return self::$realNamesByID[$userID];
- }
-
- $cacheKey = "userRealNameByID_" . $userID;
- $name = Z_Core::$MC->get($cacheKey);
- if ($name) {
- self::$realNamesByID[$userID] = $name;
- return $name;
- }
-
- $sql = "SELECT metaValue FROM users_meta WHERE userID=? AND metaKey='profile_realname'";
- $params = [$userID];
- try {
- $name = Zotero_WWW_DB_2::valueQuery($sql, $params);
- Zotero_WWW_DB_2::close();
- }
- catch (Exception $e) {
- try {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $name = Zotero_WWW_DB_1::valueQuery($sql, $params);
- Zotero_WWW_DB_1::close();
- }
- catch (Exception $e2) {
- Z_Core::logError("WARNING: " . $e2);
- }
- }
-
- if (!$name) {
- return false;
- }
-
- self::$realNamesByID[$userID] = $name;
- Z_Core::$MC->set($cacheKey, $name);
-
- return $name;
- }
-
-
- public static function toJSON($userID) {
- $realName = Zotero_Users::getRealName($userID);
- $json = [
- 'id' => $userID,
- 'username' => Zotero_Users::getUsername($userID),
- 'name' => $realName !== false ? $realName : ""
- ];
- $json['links'] = [
- 'alternate' => [
- 'href' => Zotero_URI::getUserURI($userID, true),
- 'type' => 'text/html'
- ]
- ];
-
- return $json;
- }
-
-
- public static function exists($userID) {
- if (Z_Core::$MC->get('userExists_' . $userID)) {
- return true;
- }
- $sql = "SELECT COUNT(*) FROM users WHERE userID=?";
- $exists = Zotero_DB::valueQuery($sql, $userID);
- if ($exists) {
- Z_Core::$MC->set('userExists_' . $userID, 1, 86400);
- return true;
- }
- return false;
- }
-
-
- public static function authenticate($method, $authData) {
- return call_user_func(array('Zotero_AuthenticationPlugin_' . ucwords($method), 'authenticate'), $authData);
- }
-
-
- public static function add($userID, $username='') {
- Zotero_DB::beginTransaction();
-
- $shardID = Zotero_Shards::getNextShard();
- $libraryID = Zotero_Libraries::add('user', $shardID);
-
- $sql = "INSERT INTO users (userID, libraryID, username) VALUES (?, ?, ?)";
- Zotero_DB::query($sql, array($userID, $libraryID, $username));
-
- Zotero_DB::commit();
-
- return $libraryID;
- }
-
-
- public static function addFromWWW($userID) {
- if (self::exists($userID)) {
- throw new Exception("User $userID already exists");
- }
- // Throws an error if user not found
- $username = self::getUsernameFromWWW($userID);
- self::add($userID, $username);
- }
-
-
- public static function updateFromWWW($userID) {
- // Throws an error if user not found
- $username = self::getUsernameFromWWW($userID);
- self::updateUsername($userID, $username);
- }
-
-
- public static function update($userID, $username=false) {
- $sql = "UPDATE users SET ";
- $params = array();
- if ($username) {
- $sql .= "username=?, ";
- $params[] = $username;
- }
- $sql .= "lastSyncTime=NOW() WHERE userID=?";
- $params[] = $userID;
- return Zotero_DB::query($sql, $params);
- }
-
-
- public static function updateUsername($userID, $username) {
- $sql = "UPDATE users SET username=? WHERE userID=?";
- return Zotero_DB::query(
- $sql,
- [
- $username,
- $userID
- ],
- 0,
- [
- 'writeInReadMode' => true
- ]
- );
- }
-
-
- /**
- * Get a key to represent the current state of all of a user's libraries
- */
- public static function getUpdateKey($userID, $oldStyle=false) {
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
- $parts = array();
- foreach ($libraryIDs as $libraryID) {
- if ($oldStyle) {
- $sql = "SELECT UNIX_TIMESTAMP(lastUpdated) FROM shardLibraries WHERE libraryID=?";
- }
- else {
- $sql = "SELECT version FROM shardLibraries WHERE libraryID=?";
- }
- $timestamp = Zotero_DB::valueQuery(
- $sql, $libraryID, Zotero_Shards::getByLibraryID($libraryID)
- );
- $parts[] = $libraryID . ':' . $timestamp;
- }
- return md5(implode(',', $parts));
- }
-
-
- public static function getEarliestDataTimestamp($userID) {
- $earliest = false;
-
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
- $shardIDs = Zotero_Shards::getUserShards($userID);
-
- foreach ($shardIDs as $shardID) {
- $sql = '';
- $params = array();
-
- foreach (Zotero_DataObjects::$classicObjectTypes as $type) {
- $className = 'Zotero_' . $type['plural'];
- // ClassicDataObjects
- if (method_exists($className, "field")) {
- $table = call_user_func([$className, 'field'], 'table');
- }
- else {
- $table = $className::$table;
- }
- if ($table == 'relations') {
- $field = 'serverDateModified';
- }
- else if ($table == 'settings') {
- $field = 'lastUpdated';
- }
- else {
- $field = 'dateModified';
- }
-
- $sql .= "SELECT UNIX_TIMESTAMP($table.$field) AS time FROM $table
- WHERE libraryID IN ("
- . implode(', ', array_fill(0, sizeOf($libraryIDs), '?'))
- . ") UNION ";
- $params = array_merge($params, $libraryIDs);
- }
-
- $sql = substr($sql, 0, -6) . " ORDER BY time ASC LIMIT 1";
- $time = Zotero_DB::valueQuery($sql, $params, $shardID);
- if ($time && (!$earliest || $time < $earliest)) {
- $earliest = $time;
- }
- }
-
- return $earliest;
- }
-
-
- public static function getLastStorageSync($userID) {
- $lastModified = false;
-
- $libraryIDs = Zotero_Libraries::getUserLibraries($userID);
- $shardIDs = Zotero_Shards::getUserShards($userID);
-
- foreach ($shardIDs as $shardID) {
- $sql = "SELECT UNIX_TIMESTAMP(serverDateModified) AS time FROM items
- JOIN storageFileItems USING (itemID)
- WHERE libraryID IN ("
- . implode(', ', array_fill(0, sizeOf($libraryIDs), '?'))
- . ")
- ORDER BY time DESC LIMIT 1";
- $time = Zotero_DB::valueQuery($sql, $libraryIDs, $shardID);
- if ($time > $lastModified) {
- $lastModified = $time;
- }
- }
-
- return $lastModified;
- }
-
-
- public static function isValidUser($userID) {
- if (!$userID) {
- throw new Exception("Invalid user");
- }
-
- $cacheKey = "validUser_" . $userID;
- $valid = Z_Core::$MC->get($cacheKey);
- if ($valid === 1) {
- return true;
- }
- else if ($valid === 0) {
- return false;
- }
-
- $valid = !!self::getValidUsersDB(array($userID));
-
- Z_Core::$MC->set($cacheKey, $valid ? 1 : 0, 300);
-
- return $valid;
- }
-
-
- public static function getValidUsers($userIDs) {
- if (!$userIDs) {
- return array();
- }
-
- $newUserIDs = array();
- foreach ($userIDs as $id) {
- if (Zotero_Users::isValidUser($id)) {
- $newUserIDs[] = $id;
- }
- }
-
- return $newUserIDs;
- }
-
-
- public static function getValidUsersDB($userIDs) {
- if (!$userIDs) {
- return array();
- }
-
- $invalid = array();
-
- // Get any of these users that are known to be invalid
- $sql = "SELECT UserID FROM GDN_User WHERE Banned=1 AND UserID IN ("
- . implode(', ', array_fill(0, sizeOf($userIDs), '?'))
- . ")";
-
- try {
- $invalid = Zotero_WWW_DB_2::columnQuery($sql, $userIDs);
- Zotero_WWW_DB_2::close();
- }
- catch (Exception $e) {
- try {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $invalid = Zotero_WWW_DB_1::columnQuery($sql, $userIDs);
- Zotero_WWW_DB_1::close();
- }
- catch (Exception $e2) {
- Z_Core::logError("WARNING: " . $e2);
-
- // If not available, assume valid
- }
- }
-
- if ($invalid) {
- $userIDs = array_diff($userIDs, $invalid);
- }
-
- return $userIDs;
- }
-
-
- public static function clearAllData($userID) {
- if (empty($userID)) {
- throw new Exception("userID not provided");
- }
-
- Zotero_DB::beginTransaction();
-
- $libraryID = self::getLibraryIDFromUserID($userID, 'publications');
- if ($libraryID) {
- Zotero_Libraries::clearAllData($libraryID);
- }
-
- $libraryID = self::getLibraryIDFromUserID($userID);
- Zotero_Libraries::clearAllData($libraryID);
-
- // TODO: Better handling of locked out sessions elsewhere
- $sql = "UPDATE sessions SET timestamp='0000-00-00 00:00:00',
- exclusive=0 WHERE userID=? AND exclusive=1";
- Zotero_DB::query($sql, $userID);
-
- Zotero_DB::commit();
- }
-
-
- public static function deleteUser($userID) {
- if (empty($userID)) {
- throw new Exception("userID not provided");
- }
-
- $username = Zotero_Users::getUsername($userID, true);
-
- $sql = "SELECT Deleted FROM GDN_User WHERE UserID=?";
- try {
- $deleted = Zotero_WWW_DB_2::valueQuery($sql, $userID);
- }
- catch (Exception $e) {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $deleted = Zotero_WWW_DB_1::valueQuery($sql, $userID);
- }
- if (!$deleted) {
- throw new Exception("User '$username' has not been deleted in user table");
- }
-
- Zotero_DB::beginTransaction();
-
- if (Zotero_Groups::getUserOwnedGroups($userID)) {
- throw new Exception("Cannot delete user '$username' with owned groups");
- }
-
- // Remove user from any groups they're a member of
- //
- // This isn't strictly necessary thanks to foreign key cascades,
- // but it removes some extra keyPermissions rows
- $groupIDs = Zotero_Groups::getUserGroups($userID);
- foreach ($groupIDs as $groupID) {
- $group = Zotero_Groups::get($groupID, true);
- $group->removeUser($userID);
- }
-
- // Remove all data
- Zotero_Users::clearAllData($userID);
-
- // Remove user publications library
- $libraryID = self::getLibraryIDFromUserID($userID, 'publications');
- if ($libraryID) {
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- Zotero_DB::query("DELETE FROM shardLibraries WHERE libraryID=?", $libraryID, $shardID);
- Zotero_DB::query("DELETE FROM libraries WHERE libraryID=?", $libraryID);
- }
-
- // Remove user/library rows
- $libraryID = self::getLibraryIDFromUserID($userID);
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- Zotero_DB::query("DELETE FROM shardLibraries WHERE libraryID=?", $libraryID, $shardID);
- Zotero_DB::query("DELETE FROM libraries WHERE libraryID=?", $libraryID);
-
- Zotero_DB::commit();
- }
-
-
- public static function hasPublicationsInUserLibrary($userID) {
- $sql = "SELECT COUNT(*) > 0 FROM publicationsItems JOIN items WHERE libraryID=?";
- $libraryID = self::getLibraryIDFromUserID($userID);
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- return !!Zotero_DB::valueQuery($sql, $libraryID, $shardID);
- }
-
-
- public static function hasPublicationsInLegacyLibrary($userID) {
- $libraryID = Zotero_Users::getLibraryIDFromUserID($userID, 'publications');
- if (!$libraryID) {
- return false;
- }
- $sql = "SELECT COUNT(*) > 0 FROM items WHERE libraryID=?";
- $shardID = Zotero_Shards::getByLibraryID($libraryID);
- return !!Zotero_DB::valueQuery($sql, $libraryID, $shardID);
- }
-
-
- private static function getUsernameFromWWW($userID) {
- $sql = "SELECT username FROM users WHERE userID=?";
- try {
- $username = Zotero_WWW_DB_2::valueQuery($sql, $userID);
- }
- catch (Exception $e) {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $username = Zotero_WWW_DB_1::valueQuery($sql, $userID);
- }
- if (!$username) {
- throw new Exception("User $userID not found", Z_ERROR_USER_NOT_FOUND);
- }
- return $username;
- }
-}
-?>
diff --git a/model/Utilities.inc.php b/model/Utilities.inc.php
deleted file mode 100644
index 61331abd..00000000
--- a/model/Utilities.inc.php
+++ /dev/null
@@ -1,179 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_Utilities {
- /**
- * Generates random string of given length
- *
- * @param int $length
- * @param string [$mode='lower'] 'key', 'lower', 'upper', 'mixed'
- * @param bool [$exclude_ambiguous=false] Exclude letters that are hard to distinguish visually
- **/
- public static function randomString($length, $mode='lower', $exclude_ambiguous=false) {
- // if you want extended ascii, then add the characters to the array
- $upper = array('A','B','C','D','E','F','G','H','I','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z');
- $lower = array('a','b','c','d','e','f','g','h','i','j','k','m','n','o','p','q','r','s','t','u','v','w','x','y','z');
- $numbers = array('2','3','4','5','6','7','8','9');
-
- switch ($mode) {
- // Special case for object ids, which don't use 'O'
- // (and are inadvertently missing 'L' and 'Y')
- case 'key':
- $characters = array_merge(
- array('A','B','C','D','E','F','G','H','I','J','K','M','N','P','Q','R','S','T','U','V','W','X','Z'),
- $numbers
- );
- break;
-
- case 'mixed':
- $characters = array_merge($lower, $upper, $numbers);
- if (!$exclude_ambiguous){
- $characters = array_merge($characters, array('l','1','0','O'));
- }
- break;
- case 'upper':
- $characters = array_merge($upper, $numbers);
- if (!$exclude_ambiguous){
- // This should include 'I', but the client uses it, so too late
- $characters = array_merge($characters, array('1','0','O'));
- }
- break;
-
- case 'lower':
- default:
- $characters = array_merge($lower, $numbers);
- if (!$exclude_ambiguous){
- $characters = array_merge($characters, array('l','1','0'));
- }
- break;
- }
-
- $random_str = "";
- for ($i = 0; $i < $length; $i++) {
- $random_str .= $characters[array_rand($characters)];
- }
- return $random_str;
- }
-
-
- public static function isPosInt($val) {
- // From http://us.php.net/manual/en/function.is-int.php#93560
- return ctype_digit((string) $val);
- }
-
-
- /**
- * Generate url friendly slug from name
- *
- * @param string $input name to generate slug from
- * @return string
- */
- public static function slugify($input) {
- $slug = trim($input);
- $slug = strtolower($slug);
- $slug = preg_replace("/[^a-z0-9 ._-]/", "", $slug);
- $slug = str_replace(" ", "_", $slug);
- return $slug;
- }
-
-
- public static function ellipsize($str, $len) {
- if (!$len) {
- throw new Exception("Length not specified");
- }
- if (mb_strlen($str) > $len) {
- return mb_substr($str, 0, $len) . '…';
- }
- return $str;
- }
-
-
- public static function formatJSON($jsonObj) {
- $mask = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT;
- return json_encode($jsonObj, $mask);
- }
-
-
- /**
- * Strip control characters from string
- */
- public static function cleanString($str) {
- $chars = array();
- for ($i = 0; $i < 32; $i++) {
- // Don't strip line feed and tab
- if ($i != 9 && $i != 10) {
- $chars[] = chr($i);
- }
- }
- $chars[] = chr(127);
- return str_replace($chars, '', $str);
- }
-
-
- /**
- * Recursively call cleanString() on an object's scalar properties
- */
- public static function cleanStringRecursive($obj) {
- foreach ($obj as &$val) {
- if (is_scalar($val) || $val === null) {
- if (is_string($val)) {
- $val = self::cleanString($val);
- }
- }
- else {
- self::{__FUNCTION__}($val);
- }
- }
- }
-
-
- /**
- * JavaScript-equivalent trim, which strips all Unicode whitespace
- */
- public static function unicodeTrim($str) {
- return preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u','', $str);
- }
-
-
- /**
- * Much faster implementation of array_diff, but limited to
- * comparing two arrays of integers or strings
- *
- * From http://php.net/array_diff#107928
- *
- * @return {Array} Values from array1 that aren't in array2
- */
- public static function arrayDiffFast($arrayFrom, $arrayAgainst) {
- $arrayAgainst = array_flip($arrayAgainst);
- foreach ($arrayFrom as $key => $value) {
- if (isset($arrayAgainst[$value])) {
- unset($arrayFrom[$key]);
- }
- }
- return $arrayFrom;
- }
-}
-?>
diff --git a/model/auth/Password.inc.php b/model/auth/Password.inc.php
deleted file mode 100644
index b405f39a..00000000
--- a/model/auth/Password.inc.php
+++ /dev/null
@@ -1,126 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-class Zotero_AuthenticationPlugin_Password implements Zotero_AuthenticationPlugin {
- public static function authenticate($data) {
- $salt = Z_CONFIG::$AUTH_SALT;
-
- // TODO: config
- $dev = Z_ENV_TESTING_SITE ? "_test" : "";
- $databaseName = "zotero_www{$dev}";
-
- $username = $data['username'];
- $password = $data['password'];
- $isEmailAddress = strpos($username, '@') !== false;
-
- $cacheKey = 'userAuthHash_' . hash('sha256', $username . $password);
- $userID = Z_Core::$MC->get($cacheKey);
- if ($userID) {
- return $userID;
- }
-
- // Username
- if (!$isEmailAddress) {
- $sql = "SELECT userID, username, password AS hash FROM $databaseName.users WHERE username=?";
- $params = [$username];
- }
- else {
- $sql = "SELECT userID, username, password AS hash FROM $databaseName.users
- WHERE username = ?
- UNION
- SELECT userID, username, password AS hash FROM $databaseName.users
- WHERE email = ?
- ORDER BY username = ? DESC";
- $params = [$username, $username, $username];
- }
-
- try {
- $retry = true;
- $rows = Zotero_WWW_DB_2::query($sql, $params);
- Zotero_WWW_DB_2::close();
- if (!$rows) {
- $retry = false;
- $rows = Zotero_WWW_DB_1::query($sql, $params);
- Zotero_WWW_DB_1::close();
- }
- }
- catch (Exception $e) {
- if ($retry) {
- Z_Core::logError("WARNING: $e -- retrying on primary");
- $rows = Zotero_WWW_DB_1::query($sql, $params);
- Zotero_WWW_DB_1::close();
- }
- }
-
- if (!$rows) {
- return false;
- }
-
- $found = false;
- foreach ($rows as $row) {
- // Try bcrypt
- $found = password_verify($password, $row['hash']);
-
- // Try salted SHA1
- if (!$found) {
- $found = sha1($salt . $password) == $row['hash'];
- }
-
- // Try MD5
- if (!$found) {
- $found = md5($password) == $row['hash'];
- }
-
- if ($found) {
- $foundRow = $row;
- break;
- }
- }
-
- if (!$found) {
- return false;
- }
-
- self::updateUser($foundRow['userID'], $foundRow['username']);
- Z_Core::$MC->set($cacheKey, $foundRow['userID'], 60);
- return $foundRow['userID'];
- }
-
-
- private static function updateUser($userID, $username) {
- if (Zotero_Users::exists($userID)) {
- $currentUsername = Zotero_Users::getUsername($userID, true);
- if ($currentUsername != $username) {
- Zotero_Users::update($userID, $username);
- }
- }
- else {
- Zotero_Users::add($userID, $username);
- Zotero_Users::update($userID);
- }
- }
-}
-?>
diff --git a/model/relax-ng/data9.rnc b/model/relax-ng/data9.rnc
deleted file mode 100644
index cd18cdbf..00000000
--- a/model/relax-ng/data9.rnc
+++ /dev/null
@@ -1,239 +0,0 @@
-libraryID = attribute libraryID { xsd:integer }
-key = attribute key { keyPattern }
-keyPattern = xsd:string { pattern = "[23456789ABCDEFGHIJKLMNPQRSTUVWXYZ]{8}" }
-md5Pattern = xsd:string { pattern = "[abcdefg0-9]{32}" }
-keys = list { keyPattern+ }
-dateAdded = attribute dateAdded { xsd:string { pattern = "\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])" } }
-dateModified = attribute dateModified { xsd:string { pattern = "\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])" } }
-index = attribute index { xsd:integer }
-field = element field { attribute name { text }, text }
-id = attribute id { xsd:integer }
-
-start =
- element items {
- element item {
- (
- key
- |
- (
- libraryID,
- key,
- attribute createdByUserID { xsd:integer }?,
- attribute lastModifiedByUserID { xsd:integer }?
- )
- ),
- dateAdded,
- dateModified,
- attribute deleted { xsd:boolean }?,
- (
- (
- attribute itemType { text },
- (
- field*,
- element creator {
- libraryID?,
- key,
- attribute creatorType { text },
- index,
- element creator {
- libraryID?,
- key,
- dateAdded,
- dateModified,
- (
- (
- element name { text },
- element fieldMode { "1" }
- )
- |
- (
- element firstName { text },
- element lastName { text },
- element fieldMode { "0" }?
- )
- ),
- element birthYear { xsd:integer }?
- }?
- }*
- )
- )
- |
- (
- attribute itemType { "note" },
- attribute sourceItem { keyPattern | "undefined" }?,
- element note { text }
- )
- |
- (
- attribute itemType { "attachment" },
- attribute sourceItem { keyPattern | "undefined" }?,
- attribute mimeType { text },
- attribute charset { text }?,
- (
- (
- (
- attribute linkMode { "0" | "1" | "2" },
- attribute storageModTime { xsd:integer }?,
- attribute storageHash { md5Pattern }?,
- field*,
- element path { text }
- )
- |
- (
- attribute linkMode { "3" },
- field*
- )
- ),
- element note { text }?
- )
- )
- ),
- element related {
- keys
- }?
- }*
- }?
- & element creators {
- element creator {
- libraryID?,
- key,
- dateAdded,
- dateModified,
- (
- (
- element name { text },
- element fieldMode { "1" }
- )
- |
- (
- element firstName { text },
- element lastName { text },
- element fieldMode { "0" }?
- )
- ),
- element birthYear { xsd:integer }?
- }*
- }?
- & element collections {
- element collection {
- libraryID?,
- key,
- attribute name { text },
- dateAdded,
- dateModified,
- attribute parent { keyPattern }?,
- element items {
- keys
- }?
- }+
- }?
- & element searches {
- element search {
- libraryID?,
- key,
- attribute name { text },
- dateAdded,
- dateModified,
- element condition {
- id,
- attribute condition { text },
- attribute mode { text }?,
- attribute operator { text },
- attribute value { text },
- attribute required { "0" | "1" }?
- }*
- }+
- }?
- & element tags {
- element tag {
- libraryID?,
- key,
- attribute name { text },
- attribute type { xsd:integer }?,
- dateAdded,
- dateModified,
- element items {
- keys?
- }?
- }+
- }?
- & element groups {
- element group {
- libraryID?,
- id,
- attribute name { text },
- attribute editable { "0" | "1" },
- attribute filesEditable { "0" | "1" },
- element description { text }?,
- element url { xsd:anyURI }?
- }+
- }?
- & element relations {
- element relation {
- libraryID?,
- element subject { xsd:anyURI },
- element predicate { xsd:anyURI },
- element object { xsd:anyURI }
- }+
- }?
- & element settings {
- element setting {
- libraryID,
- attribute name { text },
- attribute version { xsd:integer }?,
- text
- }+
- }?
- & element fulltexts {
- element fulltext {
- libraryID,
- key,
- attribute indexedChars { xsd:integer },
- attribute totalChars { xsd:integer },
- attribute indexedPages { xsd:integer },
- attribute totalPages { xsd:integer },
- attribute version { xsd:integer }?,
- text
- }+
- }?
- & element deleted {
- element items {
- element item {
- libraryID?, key
- }+
- }? &
- element creators {
- element creator {
- libraryID?, key
- }+
- }? &
- element collections {
- element collection {
- libraryID?, key
- }+
- }? &
- element searches {
- element search {
- libraryID?, key
- }+
- }? &
- element tags {
- element tag {
- libraryID?, key
- }+
- }? &
- element groups {
- list { xsd:integer+ }
- }? &
- element relations {
- element relation {
- libraryID?,
- attribute key {md5Pattern }
- }+
- }? &
- element settings {
- element setting {
- libraryID, attribute key { text }
- }+
- }?
- }?
diff --git a/model/relax-ng/data9.rng b/model/relax-ng/data9.rng
deleted file mode 100644
index bd2d5a27..00000000
--- a/model/relax-ng/data9.rng
+++ /dev/null
@@ -1,560 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [23456789ABCDEFGHIJKLMNPQRSTUVWXYZ]{8}
-
-
-
-
- [abcdefg0-9]{32}
-
-
-
-
-
-
-
-
- \-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])
-
-
-
-
-
-
- \-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- note
-
-
-
-
-
- undefined
-
-
-
-
-
-
-
-
-
- attachment
-
-
-
-
-
- undefined
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 1
- 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 3
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
- 0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 0
- 1
-
-
-
-
- 0
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/model/relax-ng/updated.rnc b/model/relax-ng/updated.rnc
deleted file mode 100644
index 2fe965b5..00000000
--- a/model/relax-ng/updated.rnc
+++ /dev/null
@@ -1,13 +0,0 @@
-start = element response {
- attribute timestamp { xsd:decimal },
- (
- (
- attribute version { "9" },
- element updated { external "data9.rnc" }
- )
- ),
- attribute userID { xsd:integer },
- attribute defaultLibraryID { xsd:integer },
- attribute updateKey { xsd:string { pattern = "[abcdefg0-9]{32}" } },
- attribute earliest { xsd:decimal }
-}
diff --git a/model/relax-ng/updated.rng b/model/relax-ng/updated.rng
deleted file mode 100644
index 67c56c35..00000000
--- a/model/relax-ng/updated.rng
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
- 9
-
-
-
-
-
-
-
-
-
-
-
-
-
- [abcdefg0-9]{32}
-
-
-
-
-
-
-
-
diff --git a/model/relax-ng/upload.rnc b/model/relax-ng/upload.rnc
deleted file mode 100644
index d8daa0aa..00000000
--- a/model/relax-ng/upload.rnc
+++ /dev/null
@@ -1,6 +0,0 @@
-start = element data {
- (
- attribute version { "9" },
- external "data9.rnc"
- )
-}
diff --git a/model/relax-ng/upload.rng b/model/relax-ng/upload.rng
deleted file mode 100644
index dff8070a..00000000
--- a/model/relax-ng/upload.rng
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
- 9
-
-
-
-
-
diff --git a/pa.Dockerfile b/pa.Dockerfile
new file mode 100644
index 00000000..866e95a8
--- /dev/null
+++ b/pa.Dockerfile
@@ -0,0 +1,6 @@
+
+############################
+# phpmyadmin image
+############################
+
+FROM phpmyadmin/phpmyadmin
\ No newline at end of file
diff --git a/pa.Dockerfile.dockerignore b/pa.Dockerfile.dockerignore
new file mode 100644
index 00000000..44d4ecf7
--- /dev/null
+++ b/pa.Dockerfile.dockerignore
@@ -0,0 +1,20 @@
+**/secret.json
+**/secret.txt
+**/secret.yaml
+.git
+.github
+.env
+.vscode/
+bin
+build
+client
+dataserver
+doc
+docker
+docker-compose.yml
+logs
+stream-server
+tinymce-clean-server
+Zend
+zotprime-k8s
+
diff --git a/processor/download/daemon.php b/processor/download/daemon.php
deleted file mode 100644
index b1b44187..00000000
--- a/processor/download/daemon.php
+++ /dev/null
@@ -1,15 +0,0 @@
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require("../../model/ProcessorDaemon.inc.php");
-
-$daemon = new Zotero_Download_Processor_Daemon(!empty($daemonConfig) ? $daemonConfig : array());
-$daemon->run();
-?>
diff --git a/processor/download/processor.php b/processor/download/processor.php
deleted file mode 100644
index 915bc97f..00000000
--- a/processor/download/processor.php
+++ /dev/null
@@ -1,21 +0,0 @@
-
-error_reporting(E_ALL | E_STRICT);
-set_time_limit(900);
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require('../../model/Error.inc.php');
-require('../../model/Processor.inc.php');
-
-
-$id = isset($argv[1]) ? $argv[1] : null;
-$processor = new Zotero_Download_Processor();
-$processor->run($id);
-?>
diff --git a/processor/error/daemon.php b/processor/error/daemon.php
deleted file mode 100644
index dfa42213..00000000
--- a/processor/error/daemon.php
+++ /dev/null
@@ -1,15 +0,0 @@
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require("../../model/ProcessorDaemon.inc.php");
-
-$daemon = new Zotero_Error_Processor_Daemon(!empty($daemonConfig) ? $daemonConfig : array());
-$daemon->run();
-?>
diff --git a/processor/error/processor.php b/processor/error/processor.php
deleted file mode 100644
index b658aa17..00000000
--- a/processor/error/processor.php
+++ /dev/null
@@ -1,20 +0,0 @@
-
-error_reporting(E_ALL | E_STRICT);
-set_time_limit(900);
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require('../../model/Error.inc.php');
-require('../../model/Processor.inc.php');
-
-$id = isset($argv[1]) ? $argv[1] : null;
-$processor = new Zotero_Error_Processor();
-$processor->run($id);
-?>
diff --git a/processor/upload/daemon.php b/processor/upload/daemon.php
deleted file mode 100644
index 36e67681..00000000
--- a/processor/upload/daemon.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require("../../model/ProcessorDaemon.inc.php");
-
-
-$daemon = new Zotero_Upload_Processor_Daemon(!empty($daemonConfig) ? $daemonConfig : array());
-$daemon->run();
-?>
diff --git a/processor/upload/processor.php b/processor/upload/processor.php
deleted file mode 100644
index 288f6325..00000000
--- a/processor/upload/processor.php
+++ /dev/null
@@ -1,20 +0,0 @@
-
-error_reporting(E_ALL | E_STRICT);
-set_time_limit(600);
-
-if (file_exists('../config')) {
- include('../config');
-}
-if (file_exists('./config')) {
- include('./config');
-}
-
-set_include_path("../../include");
-require("header.inc.php");
-require('../../model/Error.inc.php');
-require('../../model/Processor.inc.php');
-
-$id = isset($argv[1]) ? $argv[1] : null;
-$processor = new Zotero_Upload_Processor();
-$processor->run($id);
-?>
diff --git a/r.Dockerfile b/r.Dockerfile
new file mode 100644
index 00000000..641c8a32
--- /dev/null
+++ b/r.Dockerfile
@@ -0,0 +1,6 @@
+
+############################
+# redis image
+############################
+
+FROM redis:5.0
\ No newline at end of file
diff --git a/r.Dockerfile.dockerignore b/r.Dockerfile.dockerignore
new file mode 100644
index 00000000..44d4ecf7
--- /dev/null
+++ b/r.Dockerfile.dockerignore
@@ -0,0 +1,20 @@
+**/secret.json
+**/secret.txt
+**/secret.yaml
+.git
+.github
+.env
+.vscode/
+bin
+build
+client
+dataserver
+doc
+docker
+docker-compose.yml
+logs
+stream-server
+tinymce-clean-server
+Zend
+zotprime-k8s
+
diff --git a/stream-server b/stream-server
index fc98d3e2..7e2e57d4 160000
--- a/stream-server
+++ b/stream-server
@@ -1 +1 @@
-Subproject commit fc98d3e249bf45a1d31a899ebec1bb0ced6e9f03
+Subproject commit 7e2e57d49ad300dd595ab9e239afe908525097b4
diff --git a/sts.Dockerfile b/sts.Dockerfile
new file mode 100644
index 00000000..ac0e5501
--- /dev/null
+++ b/sts.Dockerfile
@@ -0,0 +1,13 @@
+FROM node:8.9-alpine
+ARG ZOTPRIME_VERSION=2
+
+RUN apk add --update --no-cache \
+libc6-compat
+
+WORKDIR /usr/src/app
+COPY ./stream-server/ .
+COPY docker/stream-server/default.js /usr/src/app/config/
+RUN npm install
+EXPOSE 81/TCP
+CMD [ "npm", "start" ]
+
diff --git a/sts.Dockerfile.dockerignore b/sts.Dockerfile.dockerignore
new file mode 100644
index 00000000..6a94dd94
--- /dev/null
+++ b/sts.Dockerfile.dockerignore
@@ -0,0 +1,21 @@
+**/secret.json
+**/secret.txt
+**/secret.yaml
+.env
+.git
+.github
+.vscode/
+bin
+build
+client
+dataserver
+doc
+docker/dataserver
+docker/db
+docker/miniomc
+docker-compose.yml
+logs
+#stream-server
+tinymce-clean-server
+Zend
+zotprime-k8s
diff --git a/tests/local/include/.gitignore b/tests/local/include/.gitignore
deleted file mode 100644
index 7e9e3afe..00000000
--- a/tests/local/include/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-config.inc.php
diff --git a/tests/local/include/bootstrap.inc.php b/tests/local/include/bootstrap.inc.php
deleted file mode 100644
index e4330c81..00000000
--- a/tests/local/include/bootstrap.inc.php
+++ /dev/null
@@ -1,7 +0,0 @@
-
-set_include_path(get_include_path() . PATH_SEPARATOR . "../../include");
-require_once("header.inc.php");
-
-if (!Z_ENV_TESTING_SITE) {
- throw new Exception("Tests can be run only on testing site");
-}
diff --git a/tests/local/include/config.inc.php-sample b/tests/local/include/config.inc.php-sample
deleted file mode 100644
index 159820c2..00000000
--- a/tests/local/include/config.inc.php-sample
+++ /dev/null
@@ -1,4 +0,0 @@
-
-$config = array(
- "userID" => 0
-);
diff --git a/tests/local/tests/CharacterSetsTest.php b/tests/local/tests/CharacterSetsTest.php
deleted file mode 100644
index 4c61bd74..00000000
--- a/tests/local/tests/CharacterSetsTest.php
+++ /dev/null
@@ -1,9 +0,0 @@
-assertEquals("windows-1252", $charset);
- }
-}
diff --git a/tests/local/tests/CiteTest.php b/tests/local/tests/CiteTest.php
deleted file mode 100644
index c473599a..00000000
--- a/tests/local/tests/CiteTest.php
+++ /dev/null
@@ -1,65 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class DateTests extends PHPUnit_Framework_TestCase {
- public function test_retrieveItem_should_return_correct_date_parts() {
- $item = new Zotero_Item('book');
- $item->setField('date', '2017-04-06T07:11:44Z');
- $cslItem = Zotero_Cite::retrieveItem($item);
- // "issued": {
- // "date-parts": [
- // ["2017", 4, 6]
- // ]
- // }
- $this->assertArrayHasKey('issued', $cslItem);
- $this->assertArrayHasKey('date-parts', $cslItem['issued']);
- $this->assertCount(1, $cslItem['issued']['date-parts']);
- $this->assertCount(3, $cslItem['issued']['date-parts'][0]);
- $this->assertEquals("2017", $cslItem['issued']['date-parts'][0][0]);
- $this->assertEquals(4, $cslItem['issued']['date-parts'][0][1]);
- $this->assertEquals(6, $cslItem['issued']['date-parts'][0][2]);
- }
-
- public function test_retrieveItem_should_use_first_year_from_range() {
- $item = new Zotero_Item('book');
- $item->setField('date', '2011-2012');
- $cslItem = Zotero_Cite::retrieveItem($item);
- // "issued": {
- // "date-parts": [
- // ["2011"]
- // ],
- // "season": "2012"
- // }
- $this->assertArrayHasKey('issued', $cslItem);
- $this->assertArrayHasKey('date-parts', $cslItem['issued']);
- $this->assertCount(1, $cslItem['issued']['date-parts']);
- $this->assertCount(1, $cslItem['issued']['date-parts'][0]);
- $this->assertEquals("2011", $cslItem['issued']['date-parts'][0][0]);
- $this->assertArrayHasKey('season', $cslItem['issued']);
- $this->assertEquals('2012', $cslItem['issued']['season']);
- }
-}
diff --git a/tests/local/tests/DBTest.php b/tests/local/tests/DBTest.php
deleted file mode 100644
index e1097800..00000000
--- a/tests/local/tests/DBTest.php
+++ /dev/null
@@ -1,164 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class DBTests extends PHPUnit_Framework_TestCase {
- public function setUp() {
- Zotero_DB::query("DROP TABLE IF EXISTS test");
- }
-
- public function tearDown() {
- Zotero_DB::query("DROP TABLE IF EXISTS test");
- Zotero_DB::query("SET wait_timeout = 28800");
- }
-
- public function testCloseDB() {
- Zotero_DB::query("SET @foo='bar'");
- $this->assertEquals("bar", Zotero_DB::valueQuery("SELECT @foo"));
- Zotero_DB::close();
-
- sleep(3);
-
- // The false check ensures this is a different connection
- $this->assertEquals(false, Zotero_DB::valueQuery("SELECT @foo"));
- }
-
- public function testAutoReconnect() {
- Zotero_DB::query("SET wait_timeout = 1");
-
- Zotero_DB::query("SET @foo='bar'");
- $this->assertEquals("bar", Zotero_DB::valueQuery("SELECT @foo"));
-
- sleep(3);
-
- try {
- Zotero_DB::valueQuery("SELECT @foo");
- $fail = true;
- }
- catch (Exception $e) {
- $this->assertContains("MySQL server has gone away", $e->getMessage());
- }
-
- if (isset($fail)) {
- $this->fail("Reconnect should not be automatic");
- }
-
- Zotero_DB::close();
- $this->assertEquals(false, Zotero_DB::valueQuery("SELECT @foo"));
- }
-
- public function testLastInsertIDFromStatement() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, foo2 INTEGER NOT NULL)");
- $sql = "INSERT INTO test VALUES (NULL, ?)";
- $stmt = Zotero_DB::getStatement($sql, true);
- $insertID = Zotero_DB::queryFromStatement($stmt, array(1));
- $this->assertEquals($insertID, 1);
- $insertID = Zotero_DB::queryFromStatement($stmt, array(2));
- $this->assertEquals($insertID, 2);
- }
-
- public function testNull() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER NULL, foo2 INTEGER NULL DEFAULT NULL)");
- Zotero_DB::query("INSERT INTO test VALUES (?,?)", array(null, 3));
- $result = Zotero_DB::query("SELECT * FROM test WHERE foo=?", null);
- $this->assertNull($result[0]['foo']);
- $this->assertEquals($result[0]['foo2'], 3);
- }
-
- public function testPreparedStatement() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER NULL, foo2 INTEGER NULL DEFAULT NULL)");
- $stmt = Zotero_DB::getStatement("INSERT INTO test (foo) VALUES (?)");
- $stmt->execute(array(1));
- $stmt->execute(array(2));
- $result = Zotero_DB::columnQuery("SELECT foo FROM test");
- $this->assertEquals($result[0], 1);
- $this->assertEquals($result[1], 2);
- }
-
- public function testValueQuery_Null() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER NULL)");
- Zotero_DB::query("INSERT INTO test VALUES (NULL)");
- $val = Zotero_DB::valueQuery("SELECT * FROM test");
- $this->assertNull($val);
- }
-
- public function testQuery_boundZero() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER, foo2 INTEGER)");
- Zotero_DB::query("INSERT INTO test VALUES (1, 0)");
- $this->assertEquals(Zotero_DB::valueQuery("SELECT foo FROM test WHERE foo2=?", 0), 1);
- }
-
- public function testBulkInsert() {
- Zotero_DB::query("CREATE TABLE test (foo INTEGER, foo2 INTEGER)");
- $sql = "INSERT INTO test VALUES ";
- $sets = array(
- array(1,2),
- array(2,3),
- array(3,4),
- array(4,5),
- array(5,6),
- array(6,7)
- );
-
- // Different maxInsertGroups values
- for ($i=1; $i<8; $i++) {
- Zotero_DB::bulkInsert($sql, $sets, $i);
- $rows = Zotero_DB::query("SELECT * FROM test");
- $this->assertEquals(sizeOf($rows), sizeOf($sets));
- $rowVals = array();
- foreach ($rows as $row) {
- $rowVals[] = array($row['foo'], $row['foo2']);
- }
- $this->assertEquals($rowVals, $sets);
-
- Zotero_DB::query("DELETE FROM test");
- }
-
- // First val
- $sets2 = array();
- $sets2Comp = array();
- foreach ($sets as $set) {
- $sets2[] = $set[1];
- $sets2Comp[] = array(1, $set[1]);
- }
- Zotero_DB::bulkInsert($sql, $sets2, 2, 1);
- $rows = Zotero_DB::query("SELECT * FROM test");
- $this->assertEquals(sizeOf($rows), sizeOf($sets2Comp));
- $rowVals = array();
- foreach ($rows as $row) {
- $rowVals[] = array($row['foo'], $row['foo2']);
- }
- $this->assertEquals($rowVals, $sets2Comp);
- }
-
- public function testID() {
- $id = Zotero_ID_DB_1::valueQuery("SELECT id FROM items");
- $this->assertNotEquals(false, $id);
-
- $id = Zotero_ID_DB_2::valueQuery("SELECT id FROM items");
- $this->assertNotEquals(false, $id);
- }
-}
diff --git a/tests/local/tests/Data/CreatorsTest.php b/tests/local/tests/Data/CreatorsTest.php
deleted file mode 100644
index 65d6ae60..00000000
--- a/tests/local/tests/Data/CreatorsTest.php
+++ /dev/null
@@ -1,148 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class CreatorsTests extends PHPUnit_Framework_TestCase {
- public function testGetDataValuesFromXML() {
- $xml = <<<'EOD'
-
-
-
- A.
- Testperson
-
-
- B
- Téstër
-
-
- Center før History and New Media
- 1
-
-
-
-
- BBBBBBBB
-
-
-
-EOD;
- $xml = new SimpleXMLElement($xml);
- $domSXE = dom_import_simplexml($xml->creators);
- $doc = new DOMDocument();
- $domSXE = $doc->importNode($domSXE, true);
- $domSXE = $doc->appendChild($domSXE);
-
- $objs = Zotero_Creators::getDataValuesFromXML($doc);
-
- usort($objs, function ($a, $b) {
- if ($a->lastName == $b->lastName) {
- return 0;
- }
-
- return ($a->lastName < $b->lastName) ? -1 : 1;
- });
-
- $this->assertEquals(sizeOf($objs), 3);
- $this->assertEquals($objs[0]->fieldMode, 1);
- $this->assertEquals($objs[0]->firstName, "");
- $this->assertEquals($objs[0]->lastName, "Center før History and New Media");
- $this->assertEquals($objs[0]->birthYear, null);
-
- $this->assertEquals($objs[1]->fieldMode, 0);
- $this->assertEquals($objs[1]->firstName, "A.");
- $this->assertEquals($objs[1]->lastName, "Testperson");
- $this->assertEquals($objs[1]->birthYear, null);
-
- $this->assertEquals($objs[2]->fieldMode, 0);
- $this->assertEquals($objs[2]->firstName, "B");
- $this->assertEquals($objs[2]->lastName, "Téstër");
- $this->assertEquals($objs[2]->birthYear, null);
- }
-
-
- public function testGetLongDataValueFromXML() {
- $longName = 'Longfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellowlongfellow';
-
- $xml = <<
-
-
- A.
- Testperson
-
-
- B
- $longName
-
-
- Center før History and New Media
- 1
-
-
-
-EOD;
-
- $doc = new DOMDocument();
- $doc->loadXML($xml);
- $node = Zotero_Creators::getLongDataValueFromXML($doc);
- $this->assertEquals($node->nodeName, 'lastName');
- $this->assertEquals($node->nodeValue, $longName);
-
- $xml = <<
-
-
- $longName
- Testperson
-
-
-
-EOD;
-
- $doc = new DOMDocument();
- $doc->loadXML($xml);
- $node = Zotero_Creators::getLongDataValueFromXML($doc);
- $this->assertEquals($node->nodeName, 'firstName');
- $this->assertEquals($node->nodeValue, $longName);
-
- $xml = <<
-
-
- $longName
-
-
-
-EOD;
-
- $doc = new DOMDocument();
- $doc->loadXML($xml);
- $node = Zotero_Creators::getLongDataValueFromXML($doc);
- $this->assertEquals($node->nodeName, 'name');
- $this->assertEquals($node->nodeValue, $longName);
- }
-}
diff --git a/tests/local/tests/Data/ItemTest.php b/tests/local/tests/Data/ItemTest.php
deleted file mode 100644
index bb57fe63..00000000
--- a/tests/local/tests/Data/ItemTest.php
+++ /dev/null
@@ -1,139 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class ItemTests extends PHPUnit_Framework_TestCase {
- protected static $config;
-
- public static function setUpBeforeClass() {
- require("include/config.inc.php");
- self::$config = $config;
- self::$config['userLibraryID'] = Zotero_Users::getLibraryIDFromUserID($config['userID']);
- }
-
- public function setUp() {
- Zotero_Users::clearAllData(self::$config['userID']);
- }
-
-
- public function testSetItemType() {
- $itemTypeName = "book";
- $itemTypeID = Zotero_ItemTypes::getID($itemTypeName);
-
- $item = new Zotero_Item;
- $item->libraryID = self::$config['userLibraryID'];
- $item->itemTypeID = $itemTypeID;
- $item->save();
- $this->assertEquals($itemTypeID, $item->itemTypeID);
-
- $item = new Zotero_Item($itemTypeName);
- $item->libraryID = self::$config['userLibraryID'];
- $item->save();
- $this->assertEquals($itemTypeID, $item->itemTypeID);
-
- $item = new Zotero_Item($itemTypeID);
- $item->libraryID = self::$config['userLibraryID'];
- $item->save();
- $this->assertEquals($itemTypeID, $item->itemTypeID);
- }
-
-
- public function testSetItemKeyAfterConstructorItemType() {
- $item = new Zotero_Item(2);
- $item->libraryID = self::$config['userLibraryID'];
- try {
- $item->key = "AAAAAAAA";
- }
- catch (Exception $e) {
- $this->assertEquals("Cannot set key after item is already loaded", $e->getMessage());
- return;
- }
-
- $this->fail("Unexpected success setting item key after passing item type to Zotero.Item constructor");
- }
-
-
- public function testChangeItemTypeByLibraryAndKey() {
- $item = new Zotero_Item(2);
- $item->libraryID = self::$config['userLibraryID'];
- $item->save();
- $key = $item->key;
- $this->assertEquals(2, $item->itemTypeID);
-
- $item = Zotero_Items::getByLibraryAndKey($item->libraryID, $item->key);
- $item->itemTypeID = 3;
- $item->save();
- $this->assertEquals(3, $item->itemTypeID);
- }
-
-
- public function testChangeItemTypeByConstructor() {
- $item = new Zotero_Item(2);
- $item->libraryID = self::$config['userLibraryID'];
- $item->save();
- $key = $item->key;
- $this->assertEquals(2, $item->itemTypeID);
-
- $item = new Zotero_Item;
- $item->libraryID = self::$config['userLibraryID'];
- $item->key = $key;
- $item->itemTypeID = 3;
- $item->save();
- $this->assertEquals(3, $item->itemTypeID);
- }
-
-
- public function testItemVersionAfterSave() {
- $item = new Zotero_Item("book");
- $item->libraryID = self::$config['userLibraryID'];
- $item->save();
- $this->assertEquals(0, $item->itemVersion);
-
- $item->itemTypeID = 3;
- $item->save();
- $this->assertEquals(1, $item->itemVersion);
-
- $item->setField("title", "Foo");
- $item->save();
- $this->assertEquals(2, $item->itemVersion);
- }
-
-
- public function testNumAttachments() {
- $item = new Zotero_Item;
- $item->libraryID = self::$config['userLibraryID'];
- $item->itemTypeID = Zotero_ItemTypes::getID("book");
- $item->save();
- $this->assertEquals(0, $item->numAttachments());
-
- $attachmentItem = new Zotero_Item;
- $attachmentItem->libraryID = self::$config['userLibraryID'];
- $attachmentItem->itemTypeID = Zotero_ItemTypes::getID("attachment");
- $attachmentItem->setSource($item->id);
- $attachmentItem->save();
- $this->assertEquals(1, $item->numAttachments());
- }
-}
diff --git a/tests/local/tests/Data/ItemsTest.php b/tests/local/tests/Data/ItemsTest.php
deleted file mode 100644
index f6700a6a..00000000
--- a/tests/local/tests/Data/ItemsTest.php
+++ /dev/null
@@ -1,132 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class ItemsTests extends PHPUnit_Framework_TestCase {
- protected static $config;
-
- public static function setUpBeforeClass() {
- require("include/config.inc.php");
- self::$config = $config;
- self::$config['userLibraryID'] = Zotero_Users::getLibraryIDFromUserID($config['userID']);
- }
-
- public function setUp() {
- Zotero_Users::clearAllData(self::$config['userID']);
- }
-
-
- public function testExistsByLibraryAndKey() {
- $this->assertFalse(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], "AAAAAAAA"));
-
- $item = new Zotero_Item;
- $item->libraryID = self::$config['userLibraryID'];
- $item->itemTypeID = Zotero_ItemTypes::getID("book");
- $item->save();
- $key = $item->key;
-
- $this->assertTrue(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], $key));
-
- Zotero_Items::delete(self::$config['userLibraryID'], $key);
-
- $this->assertFalse(Zotero_Items::existsByLibraryAndKey(self::$config['userLibraryID'], $key));
- }
-
-
- public function testGetDataValuesFromXML() {
- $xml = <<<'EOD'
-
-
- -
-
Foo
- Bar bar bar
-Bar bar
-
-
- Irrelevant
- Creator
-
-
-
- -
-
Test_Filename.pdf
-
- -
-
Tést 汉字漢字
- 38
- 546-553
- 1990-06-00 May - Jun., 1990
-
-
-
-EOD;
- $xml = new SimpleXMLElement($xml);
- $domSXE = dom_import_simplexml($xml->items);
- $doc = new DOMDocument();
- $domSXE = $doc->importNode($domSXE, true);
- $domSXE = $doc->appendChild($domSXE);
-
- $values = Zotero_Items::getDataValuesFromXML($doc);
- sort($values);
- $this->assertEquals(sizeOf($values), 7);
- $this->assertEquals($values[0], "1990-06-00 May - Jun., 1990");
- $this->assertEquals($values[1], "38");
- $this->assertEquals($values[2], "546-553");
- $this->assertEquals($values[3], "Bar bar bar\nBar bar");
- $this->assertEquals($values[4], "Foo");
- $this->assertEquals($values[5], "Test_Filename.pdf");
- $this->assertEquals($values[6], "Tést 汉字漢字");
- }
-
-
- public function testGetLongDataValueFromXML() {
- $longStr = str_pad("", 65534, "-") . "\nFoobar";
- $xml = <<
-
- -
-
Test_Filename.pdf
-
- -
-
Foo
- $longStr
-
-
- Irrelevant
- Creator
-
-
-
-
-
-EOD;
- $doc = new DOMDocument();
- $doc->loadXML($xml);
- $node = Zotero_Items::getLongDataValueFromXML($doc);
- $this->assertEquals("abstractNote", $node->getAttribute('name'));
- $this->assertEquals($longStr, $node->nodeValue);
- }
-}
diff --git a/tests/local/tests/Data/TagsTest.php b/tests/local/tests/Data/TagsTest.php
deleted file mode 100644
index 218774ea..00000000
--- a/tests/local/tests/Data/TagsTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class TagsTests extends PHPUnit_Framework_TestCase {
- /*public function testGetDataValuesFromXML() {
- $xml = <<<'EOD'
-
-
-
-
- AAAAAAAA
-
-
-
-
-
-
-
-
-EOD;
- $xml = new SimpleXMLElement($xml);
- $domSXE = dom_import_simplexml($xml->tags);
- $doc = new DOMDocument();
- $domSXE = $doc->importNode($domSXE, true);
- $domSXE = $doc->appendChild($domSXE);
-
- $values = Zotero_Tags::getDataValuesFromXML($doc);
- sort($values);
- $this->assertEquals(5, sizeOf($values));
- $this->assertEquals("Animal", $values[0]);
- $this->assertEquals("Mineral", $values[1]);
- $this->assertEquals("Minéral", $values[2]);
- $this->assertEquals("Vegetable", $values[3]);
- $this->assertEquals("mineral", $values[4]);
- }*/
-
-
- public function testGetLongDataValueFromXML() {
- $longTag = "Longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong";
- $xml = <<
-
-
-
- AAAAAAAA
-
-
-
-
-EOD;
- $doc = new DOMDocument();
- $doc->loadXML($xml);
- $tag = Zotero_Tags::getLongDataValueFromXML($doc);
- $this->assertEquals($longTag, $tag);
- }
-}
diff --git a/tests/local/tests/DateTest.php b/tests/local/tests/DateTest.php
deleted file mode 100644
index ffcee9d1..00000000
--- a/tests/local/tests/DateTest.php
+++ /dev/null
@@ -1,111 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class DateTests extends PHPUnit_Framework_TestCase {
- public function test_strToDate() {
- $patterns = array(
- "February 28, 2011",
- "2011-02-28",
- "28-02-2011",
- "Feb 28 2011",
- "28 Feb 2011"
- );
-
- foreach ($patterns as $pattern) {
- $parts = Zotero_Date::strToDate($pattern);
- $this->assertEquals(2011, $parts['year']);
- $this->assertEquals(2, $parts['month']);
- $this->assertEquals(28, $parts['day']);
- $this->assertFalse(isset($parts['part']));
- }
- }
-
-
- public function test_strToDate_monthYear() {
- $patterns = array(
- //"9/10",
- //"09/10",
- //"9/2010",
- //"09/2010",
- //"09-2010",
- "September 2010",
- "Sep 2010",
- "Sep. 2010"
- );
-
- foreach ($patterns as $pattern) {
- $parts = Zotero_Date::strToDate($pattern);
- $this->assertEquals(2010, $parts['year']);
- $this->assertEquals(9, $parts['month']);
- $this->assertFalse(isset($parts['day']));
- $this->assertFalse(isset($parts['part']));
- }
- }
-
-
- public function test_strToDate_yearRange() {
- $pattern = "1983-84";
- $parts = Zotero_Date::strToDate($pattern);
- $this->assertEquals(1983, $parts['year']);
- $this->assertFalse(isset($parts['month']));
- $this->assertFalse(isset($parts['day']));
- $this->assertEquals("84", $parts['part']);
-
- $pattern = "1983-1984";
- $parts = Zotero_Date::strToDate($pattern);
- $this->assertEquals(1983, $parts['year']);
- $this->assertFalse(isset($parts['month']));
- $this->assertFalse(isset($parts['day']));
- $this->assertEquals("1984", $parts['part']);
- }
-
-
- /*public function test_strToDate_BCE() {
- $patterns = array(
- "c380 BC/1935",
- "2009 BC",
- "2009 B.C.",
- "2009 BCE",
- "2009 B.C.E.",
- "2009BC",
- "2009BCE",
- "2009B.C.",
- "2009B.C.E.",
- "c2009BC",
- "c2009BCE",
- "~2009BC",
- "~2009BCE",
- "-300"
- );
-
- foreach ($patterns as $pattern) {
- $parts = Zotero_Date::strToDate($pattern);
- var_dump($parts['year']);
- $this->assertTrue(!!preg_match("/^[0-9]+$/", $parts['year']));
- }
- }*/
-}
diff --git a/tests/local/tests/MemcacheTest.php b/tests/local/tests/MemcacheTest.php
deleted file mode 100644
index b1c519f3..00000000
--- a/tests/local/tests/MemcacheTest.php
+++ /dev/null
@@ -1,140 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class MemcacheTests extends PHPUnit_Framework_TestCase {
- public function testQueue() {
- // Clean up
- Z_Core::$MC->rollback(true);
- Z_Core::$MC->delete("testFoo");
- Z_Core::$MC->delete("testFoo2");
- Z_Core::$MC->delete("testFoo3");
- Z_Core::$MC->delete("testDeleted");
-
- // Used below
- Z_Core::$MC->set("testDeleted1", "foo1");
-
- Z_Core::$MC->begin();
- Z_Core::$MC->set("testFoo", "bar");
- Z_Core::$MC->set("testFoo", "bar2");
- Z_Core::$MC->add("testFoo", "bar3"); // should be ignored
-
- Z_Core::$MC->add("testFoo2", "bar4");
-
- Z_Core::$MC->add("testFoo3", "bar5");
- Z_Core::$MC->set("testFoo3", "bar6");
-
- // Gets within a transaction should return the queued value
- $this->assertEquals(Z_Core::$MC->get("testFoo"), "bar2");
- $this->assertEquals(Z_Core::$MC->get("testFoo2"), "bar4");
- $this->assertEquals(Z_Core::$MC->get("testFoo3"), "bar6");
-
- // Multi-gets within a transaction should return the queued value
- $arr = array("testFoo" => "bar2", "testFoo2" => "bar4", "testFoo3" => "bar6");
- $this->assertEquals(Z_Core::$MC->get(array("testFoo", "testFoo2", "testFoo3")), $arr);
-
- // Gets for a deleted key within the transaction should return false,
- // whether the key was set before or during the transaction
- Z_Core::$MC->set("testDeleted2", "foo2");
- Z_Core::$MC->delete("testDeleted1");
- $this->assertFalse(Z_Core::$MC->get("testDeleted1"));
- Z_Core::$MC->delete("testDeleted2");
- $this->assertFalse(Z_Core::$MC->get("testDeleted2"));
-
- Z_Core::$MC->commit();
-
- $this->assertEquals(Z_Core::$MC->get("testFoo"), "bar2");
- $this->assertEquals(Z_Core::$MC->get("testFoo2"), "bar4");
- $this->assertEquals(Z_Core::$MC->get("testFoo3"), "bar6");
- $this->assertFalse(Z_Core::$MC->get("testDeleted"));
-
- // Clean up
- Z_Core::$MC->delete("testFoo");
- Z_Core::$MC->delete("testFoo2");
- Z_Core::$MC->delete("testFoo3");
- Z_Core::$MC->delete("testDeleted");
- }
-
- public function testUnicode() {
- // Clean up
- Z_Core::$MC->rollback(true);
- Z_Core::$MC->delete("testUnicode1");
- Z_Core::$MC->delete("testUnicode2");
-
- $str1 = "Øüévrê";
- $str2 = "汉字漢字";
-
- Z_Core::$MC->set("testUnicode1", $str1 . $str2);
- $this->assertEquals(Z_Core::$MC->get("testUnicode1"), $str1 . $str2);
-
- $arr = array('foo1' => $str1, 'foo2' => $str2);
- Z_Core::$MC->set("testUnicode2", $arr);
- $this->assertEquals(Z_Core::$MC->get("testUnicode2"), $arr);
-
- // Clean up
- Z_Core::$MC->delete("testUnicode1");
- Z_Core::$MC->delete("testUnicode2");
- }
-
- public function testNonExistent() {
- // Clean up
- Z_Core::$MC->rollback(true);
- Z_Core::$MC->delete("testMissing");
- Z_Core::$MC->delete("testZero");
-
- Z_Core::$MC->set("testZero", 0);
-
- $this->assertFalse(Z_Core::$MC->get("testMissing"));
- $this->assertTrue(0 === Z_Core::$MC->get("testZero"));
-
- // Clean up
- Z_Core::$MC->delete("testZero");
- }
-
- public function testMultiGet() {
- // Clean up
- Z_Core::$MC->rollback(true);
- Z_Core::$MC->delete("testFoo");
- Z_Core::$MC->delete("testFoo2");
- Z_Core::$MC->delete("testFoo3");
-
- Z_Core::$MC->set("testFoo", "bar");
- Z_Core::$MC->set("testFoo2", "bar2");
- Z_Core::$MC->set("testFoo3", "bar3");
-
- $arr = array(
- "testFoo" => "bar",
- "testFoo2" => "bar2",
- "testFoo3" => "bar3"
- );
- $this->assertEquals(Z_Core::$MC->get(array("testFoo", "testFoo2", "testFoo3")), $arr);
-
- // Clean up
- Z_Core::$MC->delete("testFoo");
- Z_Core::$MC->delete("testFoo2");
- Z_Core::$MC->delete("testFoo3");
- }
-}
diff --git a/tests/local/tests/NotifierTest.php b/tests/local/tests/NotifierTest.php
deleted file mode 100644
index 45764b76..00000000
--- a/tests/local/tests/NotifierTest.php
+++ /dev/null
@@ -1,138 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class NotifierTests extends PHPUnit_Framework_TestCase {
- public function testNotify() {
- $event = "modify";
- $type = "item";
- $libraryKey = "4/DDDDDDDD";
-
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->once())
- ->method('notify')
- ->with(
- $event,
- $type,
- array($libraryKey)
- );
-
- $hash = Zotero_Notifier::registerObserver($mock, $type);
- $this->assertEquals(2, strlen($hash));
-
- Zotero_Notifier::trigger($event, $type, $libraryKey);
-
- Zotero_Notifier::unregisterObserver($hash);
- }
-
-
- public function testNotifyMultipleLibraryKeys() {
- $event = "add";
- $type = "item";
- $libraryKeys = array("1/ABCD2345", "1/BCDE3456", "1/CDEF4567");
-
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->once())
- ->method('notify')
- ->with(
- $event,
- $type,
- $libraryKeys
- );
-
- $hash = Zotero_Notifier::registerObserver($mock, $type);
-
- Zotero_Notifier::trigger($event, $type, $libraryKeys);
-
- Zotero_Notifier::unregisterObserver($hash);
- }
-
-
- public function testSkipType() {
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->never())->method('notify');
-
- $hash = Zotero_Notifier::registerObserver($mock, "item");
- Zotero_Notifier::trigger("add", "collection", "2/ABABABAB");
-
- Zotero_Notifier::unregisterObserver($hash);
- }
-
-
- public function testUnregisterObserver() {
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->never())->method('notify');
-
- $hash = Zotero_Notifier::registerObserver($mock, "item");
- Zotero_Notifier::unregisterObserver($hash);
-
- Zotero_Notifier::trigger("add", "item", "3/CADACADA");
- }
-
-
- public function testQueue() {
- $event = "add";
- $type = "item";
- $keys = array("1/AAAAAAAA", "1/BBBBBBBB");
-
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->once())
- ->method('notify')
- ->with(
- $event,
- $type,
- array($keys[0], $keys[1])
- );
-
- $hash = Zotero_Notifier::registerObserver($mock, $type);
-
- Zotero_Notifier::begin();
- Zotero_Notifier::trigger($event, $type, $keys[0]);
- Zotero_Notifier::trigger($event, $type, $keys[1]);
- Zotero_Notifier::commit();
-
- Zotero_Notifier::unregisterObserver($hash);
- }
-
-
- public function testReset() {
- $event = "add";
- $type = "item";
-
- $mock = $this->getMock('stdClass', array('notify'));
- $mock->expects($this->never())->method('notify');
-
- $hash = Zotero_Notifier::registerObserver($mock, $type);
-
- Zotero_Notifier::begin();
- Zotero_Notifier::trigger($event, $type, "1/AAAAAAAA");
- Zotero_Notifier::trigger($event, $type, "1/BBBBBBBB");
- Zotero_Notifier::reset();
- Zotero_Notifier::commit();
-
- Zotero_Notifier::unregisterObserver($hash);
- }
-}
diff --git a/tests/local/tests/UnicodeTest.php b/tests/local/tests/UnicodeTest.php
deleted file mode 100644
index 52d52133..00000000
--- a/tests/local/tests/UnicodeTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class UnicodeTests extends PHPUnit_Framework_TestCase {
- public function testConvert() {
- $original = "Abcdefg Âéìøü 这是一个测试。";
- $str = Zotero_Attachments::encodeRelativeDescriptorString($original);
- // assert
- $str = Zotero_Attachments::decodeRelativeDescriptorString($str);
- $this->assertEquals($original, $str);
- }
-}
diff --git a/tests/local/tests/UsersTest.php b/tests/local/tests/UsersTest.php
deleted file mode 100644
index 9910c6ea..00000000
--- a/tests/local/tests/UsersTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2010 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-require_once 'include/bootstrap.inc.php';
-
-class UsersTests extends PHPUnit_Framework_TestCase {
- public function testExists() {
- $this->assertTrue(Zotero_Users::exists(1));
- $this->assertFalse(Zotero_Users::exists(100));
- }
-
- public function testAuthenticate() {
- $this->assertEquals(1, Zotero_Users::authenticate('password', ['username'=>'testuser', 'password'=>'letmein']));
- $this->assertFalse(Zotero_Users::authenticate('password', ['username'=>'testuser', 'password'=>'letmein2']));
- }
-}
diff --git a/tests/remote/data/bad_string.xml b/tests/remote/data/bad_string.xml
deleted file mode 100644
index 5cf31734..00000000
--- a/tests/remote/data/bad_string.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<p> </p>
-<p style="margin-top: 0.18cm; margin-bottom: 0.18cm; line-height: 100%;" lang="es-ES"><br /><br /></p>
-<p style="margin-top: 0.18cm; margin-bottom: 0.18cm; line-height: 100%;" lang="es-ES"><br /><br /></p>
-<table border="1" cellspacing="0" cellpadding="7" width="614">
-<colgroup><col width="598"></col> </colgroup>
-<p style="margin-top: 0.18cm; margin-bottom: 0.18cm;" lang="en-US"><span style="font-family: Times New Roman,serif;"><span style="font-size: x-large;"><strong>test</strong></span></span></p>
diff --git a/tests/remote/data/sync1download.xml b/tests/remote/data/sync1download.xml
deleted file mode 100644
index 97be9141..00000000
--- a/tests/remote/data/sync1download.xml
+++ /dev/null
@@ -1,123 +0,0 @@
-
-
-
-
-
- Døn
- Stîllmån
-
-
- 汉字
- 1
-
-
- Test
- Testerman
-
-
- Testish McTester
- 1
-
-
- Testy
- Teststein
-
-
-
- -
-
<p>Here's a <strong>child</strong> note.</p>
-
- -
-
3
- March 6, 2007
- My Book Section
-
-
- DUQPU87V
-
- -
-
amazon.html
- AAAAAAFkAAIAAAxNYWNpbnRvc2ggSEQAAAAAAAAAAAAAAAAAAADC/J9aSCsAAAAOrpkLYW1hem9uLmh0bWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADnT/bcS9TKEAAAAAAAAAAP////8AAAkgAAAAAAAAAAAAAAAAAAAAB0Rlc2t0b3AAABAACAAAwvzXmgAAABEACAAAxL2E4QAAAAEADAAOrpkADjPnAA4z5QACACpNYWNpbnRvc2ggSEQ6VXNlcnM6ZGFuOkRlc2t0b3A6YW1hem9uLmh0bWwADgAYAAsAYQBtAGEAegBvAG4ALgBoAHQAbQBsAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAHVVzZXJzL2Rhbi9EZXNrdG9wL2FtYXpvbi5odG1sAAATAAEvAAAVAAIACv//AAA=
- <p>Note on a top-level linked file</p>
- DUQPU87V
-
- -
-
http://chnm.gmu.edu/
- 2009-03-07 04:55:59
- Center for History and New Media
- storage:chnm.gmu.edu.html
- <p>This is a note for a snapshot.</p>
-
- -
-
<p>Here's a top-level note.</p>
-
- -
-
http://www.zotero.org/
- 2009-03-07 04:56:47
- Zotero: The Next-Generation Research Tool
- storage:www.zotero.org.html
-
- -
-
My Book
-
-
- 6TKKAABJ 7IMJZ8V6
-
- -
-
http://chnm.gmu.edu/
- 2009-03-07 04:56:01
- Center for History and New Media
- <p>This is a note for a link.</p>
-
- -
-
FILE.jpg
- storage:FILE.jpg
-
- -
-
Trashed item
-
-
-
-
-
- DUQPU87V
-
-
- HTHD884W
-
-
- 6TKKAABJ 9P9UVFK3
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 6TKKAABJ
-
-
- 6TKKAABJ
-
-
- 6TKKAABJ DUQPU87V
-
-
- DUQPU87V
-
-
- HTHD884W
-
-
- T3K4BNWP
-
-
-
-
diff --git a/tests/remote/data/sync1upload.xml b/tests/remote/data/sync1upload.xml
deleted file mode 100644
index ca0947eb..00000000
--- a/tests/remote/data/sync1upload.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
- Test
- Testerman
-
-
- 汉字
- 1
-
-
- Testy
- Teststein
-
-
- Døn
- Stîllmån
-
-
-
- -
-
3
- 2007-03-06 March 6, 2007
- My Book Section
-
-
-
- Testish McTester
- 1
-
-
-
- -
-
My Book
-
-
- 6TKKAABJ
-
- -
-
<p>Here's a <strong>child</strong> note.</p>
-
- -
-
http://chnm.gmu.edu/
- 2009-03-07 04:56:01
- Center for History and New Media
- <p>This is a note for a link.</p>
-
- -
-
http://chnm.gmu.edu/
- 2009-03-07 04:55:59
- Center for History and New Media
- storage:chnm.gmu.edu.html
- <p>This is a note for a snapshot.</p>
-
- -
-
<p>Here's a top-level note.</p>
-
- -
-
http://www.zotero.org/
- 2009-03-07 04:56:47
- Zotero: The Next-Generation Research Tool
- storage:www.zotero.org.html
-
- -
-
FILE.jpg
- storage:FILE.jpg
-
- -
-
Trashed item
-
-
- -
-
amazon.html
- AAAAAAFkAAIAAAxNYWNpbnRvc2ggSEQAAAAAAAAAAAAAAAAAAADC/J9aSCsAAAAOrpkLYW1hem9uLmh0bWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADnT/bcS9TKEAAAAAAAAAAP////8AAAkgAAAAAAAAAAAAAAAAAAAAB0Rlc2t0b3AAABAACAAAwvzXmgAAABEACAAAxL2E4QAAAAEADAAOrpkADjPnAA4z5QACACpNYWNpbnRvc2ggSEQ6VXNlcnM6ZGFuOkRlc2t0b3A6YW1hem9uLmh0bWwADgAYAAsAYQBtAGEAegBvAG4ALgBoAHQAbQBsAA8AGgAMAE0AYQBjAGkAbgB0AG8AcwBoACAASABEABIAHVVzZXJzL2Rhbi9EZXNrdG9wL2FtYXpvbi5odG1sAAATAAEvAAAVAAIACv//AAA=
- <p>Note on a top-level linked file</p>
- DUQPU87V
-
-
-
-
- 6TKKAABJ 9P9UVFK3
-
-
- DUQPU87V
-
-
-
- HTHD884W
-
-
-
-
-
-
-
-
-
-
-
-
-
- DUQPU87V
-
-
- 6TKKAABJ
-
-
- 6TKKAABJ DUQPU87V
-
-
- 6TKKAABJ
-
-
- HTHD884W
-
-
- T3K4BNWP
-
-
-
diff --git a/tests/remote/include/.gitignore b/tests/remote/include/.gitignore
deleted file mode 100644
index 2fa7ce7c..00000000
--- a/tests/remote/include/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-config.ini
diff --git a/tests/remote/include/api2.inc.php b/tests/remote/include/api2.inc.php
deleted file mode 100644
index b47be0c5..00000000
--- a/tests/remote/include/api2.inc.php
+++ /dev/null
@@ -1,778 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once __DIR__ . '/http.inc.php';
-
-use \API2 as API;
-
-class API2 {
- private static $config;
- private static $nsZAPI;
- private static $apiVersion = false;
-
- public static function loadConfig() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- self::$nsZAPI = 'http://zotero.org/ns/api';
- }
-
-
- public static function useAPIVersion($apiVersion) {
- self::$apiVersion = $apiVersion;
- }
-
-
- //
- // Item modification methods
- //
- public static function getItemTemplate($itemType) {
- $response = self::get("items/new?itemType=$itemType");
- return json_decode($response->getBody());
- }
-
-
- public static function createItem($itemType, $data=array(), $context=null, $responseFormat='atom') {
- $json = self::getItemTemplate($itemType);
-
- if ($data) {
- foreach ($data as $field => $val) {
- $json->$field = $val;
- }
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('item', $response, $responseFormat, $context);
- }
-
-
- /**
- * POST a JSON item object to the main test user's account
- * and return the response
- */
- public static function postItem($json) {
- return self::postItems(array($json));
- }
-
-
- /**
- * POST a JSON items object to the main test user's account
- * and return the response
- */
- public static function postItems($json) {
- return self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => $json
- )),
- array("Content-Type: application/json")
- );
- }
-
-
- public static function groupCreateItem($groupID, $itemType, $context=null, $responseFormat='atom') {
- $response = self::get("items/new?itemType=$itemType");
- $json = json_decode($response->getBody());
-
- $response = self::groupPost(
- $groupID,
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- if ($context) {
- $context->assert200($response);
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($responseFormat != 'json' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("Item creation failed");
- }
-
- switch ($responseFormat) {
- case 'json':
- return $json;
-
- case 'key':
- return array_shift($json['success']);
-
- case 'atom':
- $itemKey = array_shift($json['success']);
- return self::groupGetItemXML($groupID, $itemKey, $context);
-
- default:
- throw new Exception("Invalid response format '$responseFormat'");
- }
- }
-
-
- public static function createAttachmentItem($linkMode, $data=[], $parentKey=false, $context=false, $responseFormat='atom') {
- $response = self::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
- foreach ($data as $key => $val) {
- $json->{$key} = $val;
- }
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- if ($context) {
- $context->assert200($response);
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($responseFormat != 'json' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("Item creation failed");
- }
-
- switch ($responseFormat) {
- case 'json':
- return $json;
-
- case 'key':
- return array_shift($json['success']);
-
- case 'atom':
- $itemKey = array_shift($json['success']);
- $xml = self::getItemXML($itemKey, $context);
- if ($context) {
- $data = self::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $context->assertEquals($linkMode, $json->linkMode);
- }
- return $xml;
-
- return self::getXMLFromResponse($response);
-
- default:
- throw new Exception("Invalid response format '$responseFormat'");
- }
- }
-
-
- public static function groupCreateAttachmentItem($groupID, $linkMode, $data=[], $parentKey=false, $context=false, $responseFormat='atom') {
- $response = self::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
- foreach ($data as $key => $val) {
- $json->{$key} = $val;
- }
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::groupPost(
- $groupID,
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- if ($context) {
- $context->assert200($response);
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($responseFormat != 'json' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("Item creation failed");
- }
-
- switch ($responseFormat) {
- case 'json':
- return $json;
-
- case 'key':
- return array_shift($json['success']);
-
- case 'atom':
- $itemKey = array_shift($json['success']);
- $xml = self::groupGetItemXML($groupID, $itemKey, $context);
- if ($context) {
- $data = self::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $context->assertEquals($linkMode, $json->linkMode);
- }
- return $xml;
-
- default:
- throw new Exception("Invalid response format '$responseFormat'");
- }
- }
-
-
- public static function createNoteItem($text="", $parentKey=false, $context=false, $responseFormat='atom') {
- $response = self::get("items/new?itemType=note");
- $json = json_decode($response->getBody());
- $json->note = $text;
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- if ($context) {
- $context->assert200($response);
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($responseFormat != 'json' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("Item creation failed");
- }
-
- switch ($responseFormat) {
- case 'json':
- return $json;
-
- case 'key':
- return array_shift($json['success']);
-
- case 'atom':
- $itemKey = array_shift($json['success']);
- $xml = self::getItemXML($itemKey, $context);
- if ($context) {
- $data = self::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $context->assertEquals($text, $json->note);
- }
- return $xml;
-
- default:
- throw new Exception("Invalid response format '$responseFormat'");
- }
- }
-
-
- public static function createCollection($name, $data=array(), $context=null, $responseFormat='atom') {
- if (is_array($data)) {
- $parent = isset($data['parentCollection']) ? $data['parentCollection'] : false;
- $relations = isset($data['relations']) ? $data['relations'] : new stdClass;
- }
- else {
- $parent = $data ? $data : false;
- $relations = new stdClass;
- }
-
- $json = array(
- "collections" => array(
- array(
- 'name' => $name,
- 'parentCollection' => $parent,
- 'relations' => $relations
- )
- )
- );
-
- $response = self::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('collection', $response, $responseFormat, $context);
- }
-
-
- public static function createSearch($name, $conditions=array(), $context=null, $responseFormat='atom') {
- if ($conditions == 'default') {
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'test'
- )
- );
- }
-
- $json = array(
- "searches" => array(
- array(
- 'name' => $name,
- 'conditions' => $conditions
- )
- )
- );
-
- $response = self::userPost(
- self::$config['userID'],
- "searches?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('search', $response, $responseFormat, $context);
- }
-
-
- public static function getLibraryVersion() {
- $response = self::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- return $response->getHeader("Last-Modified-Version");
- }
-
-
- public static function getGroupLibraryVersion($groupID) {
- $response = self::groupGet(
- $groupID,
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- return $response->getHeader("Last-Modified-Version");
- }
-
-
- public static function getItemXML($keys, $context=null) {
- return self::getObjectXML('item', $keys, $context);
- }
-
-
- public static function groupGetItemXML($groupID, $keys, $context=null) {
- if (is_scalar($keys)) {
- $keys = array($keys);
- }
-
- $response = self::groupGet(
- $groupID,
- "items?key=" . self::$config['apiKey']
- . "&itemKey=" . implode(',', $keys) . "&order=itemKeyList"
- . "&content=json"
- );
- if ($context) {
- $context->assert200($response);
- }
- return self::getXMLFromResponse($response);
- }
-
-
- public static function getXMLFromFirstSuccessItem($response) {
- $key = self::getFirstSuccessKeyFromResponse($response);
- self::getItemXML($key);
- }
-
-
- public static function getCollectionXML($keys, $context=null) {
- return self::getObjectXML('collection', $keys, $context);
- }
-
-
- public static function getSearchXML($keys, $context=null) {
- return self::getObjectXML('search', $keys, $context);
- }
-
-
- //
- // HTTP methods
- //
- public static function get($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::get($url, $headers, $auth);
- if (self::$config['verbose'] >= 2) {
- echo "\n\n" . $response->getBody() . "\n";
- }
- return $response;
- }
-
- public static function userGet($userID, $suffix, $headers=array(), $auth=false) {
- return self::get("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function groupGet($groupID, $suffix, $headers=array(), $auth=false) {
- return self::get("groups/$groupID/$suffix", $headers, $auth);
- }
-
- public static function post($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::post($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function userPost($userID, $suffix, $data, $headers=array(), $auth=false) {
- return self::post("users/$userID/$suffix", $data, $headers, $auth);
- }
-
- public static function groupPost($groupID, $suffix, $data, $headers=array(), $auth=false) {
- return self::post("groups/$groupID/$suffix", $data, $headers, $auth);
- }
-
- public static function put($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::put($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function userPut($userID, $suffix, $data, $headers=array(), $auth=false) {
- return self::put("users/$userID/$suffix", $data, $headers, $auth);
- }
-
- public static function groupPut($groupID, $suffix, $data, $headers=array(), $auth=false) {
- return self::put("groups/$groupID/$suffix", $data, $headers, $auth);
- }
-
- public static function patch($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::patch($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function userPatch($userID, $suffix, $data, $headers=array()) {
- return self::patch("users/$userID/$suffix", $data, $headers);
- }
-
- public static function head($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::head($url, $headers, $auth);
- return $response;
- }
-
- public static function userHead($userID, $suffix, $headers=array(), $auth=false) {
- return self::head("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function delete($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- $response = HTTP::delete($url, $headers, $auth);
- return $response;
- }
-
- public static function userDelete($userID, $suffix, $headers=array(), $auth=false) {
- return self::delete("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function groupDelete($groupID, $suffix, $headers=array(), $auth=false) {
- return self::delete("groups/$groupID/$suffix", $headers, $auth);
- }
-
-
- public static function userClear($userID) {
- $response = self::userPost(
- $userID,
- "clear",
- "",
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 204) {
- var_dump($response->getBody());
- throw new Exception("Error clearing user $userID");
- }
- }
-
- public static function groupClear($groupID) {
- $response = self::groupPost(
- $groupID,
- "clear",
- "",
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 204) {
- var_dump($response->getBody());
- throw new Exception("Error clearing group $groupID");
- }
- }
-
-
- //
- // Response parsing
- //
- public static function getXMLFromResponse($response) {
- try {
- $xml = new SimpleXMLElement($response->getBody());
- }
- catch (Exception $e) {
- var_dump($response->getBody());
- throw $e;
- }
- $xml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
- $xml->registerXPathNamespace('zapi', 'http://zotero.org/ns/api');
- return $xml;
- }
-
-
- public static function getJSONFromResponse($response) {
- $json = json_decode($response->getBody(), true);
- if (is_null($json)) {
- var_dump($response->getBody());
- throw new Exception("JSON response could not be parsed");
- }
- return $json;
- }
-
-
- public static function getFirstSuccessKeyFromResponse($response) {
- $json = self::getJSONFromResponse($response);
- if (empty($json['success'])) {
- var_dump($response->getBody());
- throw new Exception("No success keys found in response");
- }
- return array_shift($json['success']);
- }
-
-
- public static function parseDataFromAtomEntry($entryXML) {
- $key = (string) array_get_first($entryXML->xpath('//atom:entry/zapi:key'));
- $version = (string) array_get_first($entryXML->xpath('//atom:entry/zapi:version'));
- $content = array_get_first($entryXML->xpath('//atom:entry/atom:content'));
- if (is_null($content)) {
- throw new Exception("Atom response does not contain ");
- }
-
- // If 'content' contains XML, serialize all subnodes
- if ($content->count()) {
- $content = $content->asXML();
- }
- // Otherwise just get string content
- else {
- $content = (string) $content;
- }
-
- return array(
- "key" => $key,
- "version" => $version,
- "content" => $content
- );
- }
-
-
- public static function getContentFromResponse($response) {
- $xml = self::getXMLFromResponse($response);
- $data = self::parseDataFromAtomEntry($xml);
- return $data['content'];
- }
-
-
- public static function setKeyOption($userID, $key, $option, $val) {
- $response = self::get(
- "users/$userID/keys/$key",
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("GET returned " . $response->getStatus());
- }
-
- try {
- $xml = new SimpleXMLElement($response->getBody());
- }
- catch (Exception $e) {
- var_dump($response->getBody());
- throw $e;
- }
- foreach ($xml->access as $access) {
- switch ($option) {
- case 'libraryNotes':
- if (!isset($access['library'])) {
- break;
- }
- $current = (int) $access['notes'];
- if ($current != $val) {
- $access['notes'] = (int) $val;
- $response = self::put(
- "users/" . self::$config['userID'] . "/keys/" . self::$config['apiKey'],
- $xml->asXML(),
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("PUT returned " . $response->getStatus());
- }
- }
- break;
-
- case 'libraryWrite':
- if (!isset($access['library'])) {
- continue;
- }
- $current = (int) $access['write'];
- if ($current != $val) {
- $access['write'] = (int) $val;
- $response = self::put(
- "users/" . self::$config['userID'] . "/keys/" . self::$config['apiKey'],
- $xml->asXML(),
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("PUT returned " . $response->getStatus());
- }
- }
- break;
- }
- }
- }
-
-
- public static function getPluralObjectType($objectType) {
- if ($objectType == 'search') {
- return $objectType . "es";
- }
- return $objectType . "s";
- }
-
-
- private static function getObjectXML($objectType, $keys, $context=null) {
- $objectTypePlural = self::getPluralObjectType($objectType);
-
- if (is_scalar($keys)) {
- $keys = array($keys);
- }
-
- $response = self::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&{$objectType}Key=" . implode(',', $keys) . "&order={$objectType}KeyList"
- . "&content=json"
- );
- if ($context) {
- $context->assert200($response);
- }
- return self::getXMLFromResponse($response);
- }
-
-
- private static function handleCreateResponse($objectType, $response, $responseFormat, $context=null) {
- $uctype = ucwords($objectType);
-
- if ($context) {
- $context->assert200($response);
- }
-
- if ($responseFormat == 'response') {
- return $response;
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($responseFormat != 'responsejson' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("$uctype creation failed");
- }
-
- if ($responseFormat == 'responsejson') {
- return $json;
- }
-
- $key = array_shift($json['success']);
-
- if ($responseFormat == 'key') {
- return $key;
- }
-
- $func = 'get' . $uctype . 'XML';
- $xml = self::$func($key, $context);
-
- if ($responseFormat == 'atom') {
- return $xml;
- }
-
- $data = self::parseDataFromAtomEntry($xml);
-
- if ($responseFormat == 'data') {
- return $data;
- }
- if ($responseFormat == 'content') {
- return $data['content'];
- }
- if ($responseFormat == 'json') {
- return json_decode($data['content'], true);
- }
-
- throw new Exception("Invalid response format '$responseFormat'");
- }
-}
-
-API2::loadConfig();
diff --git a/tests/remote/include/api3.inc.php b/tests/remote/include/api3.inc.php
deleted file mode 100644
index d591cef3..00000000
--- a/tests/remote/include/api3.inc.php
+++ /dev/null
@@ -1,1101 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once __DIR__ . '/http.inc.php';
-
-class API3 {
- private static $config;
- private static $nsZAPI;
- private static $apiVersion = false;
- private static $apiKey = false;
-
- public static function loadConfig() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- self::$nsZAPI = 'http://zotero.org/ns/api';
- }
-
-
- public static function useAPIVersion($apiVersion) {
- self::$apiVersion = $apiVersion;
- }
-
-
- public static function useAPIKey($key = "") {
- self::$apiKey = $key;
- }
-
-
- public static function createGroup($fields) {
- $xml = new \SimpleXMLElement(' ');
- $xml['owner'] = $fields['owner'];
- $xml['name'] = "Test Group " . uniqid();
- $xml['type'] = $fields['type'];
- $xml['libraryEditing'] = isset($fields['libraryEditing'])
- ? $fields['libraryEditing']
- : 'members';
- $xml['libraryReading'] = isset($fields['libraryReading'])
- ? $fields['libraryReading']
- : 'members';
- $xml['fileEditing'] = isset($fields['fileEditing'])
- ? $fields['fileEditing']
- : 'none';
- $xml['description'] = "";
- $xml['url'] = "";
- $xml['hasImage'] = false;
-
- $response = self::superPost(
- "groups",
- $xml->asXML()
- );
- if ($response->getStatus() != 201) {
- echo $response->getBody();
- throw new Exception("Unexpected response code " . $response->getStatus());
- }
- $url = $response->getHeader('Location');
- preg_match('/[0-9]+$/', $url, $matches);
- return (int) $matches[0];
- }
-
-
- public static function deleteGroup($groupID) {
- $response = self::superDelete(
- "groups/$groupID"
- );
- if ($response->getStatus() != 204) {
- echo $response->getBody();
- throw new Exception("Unexpected response code " . $response->getStatus());
- }
- }
-
-
- public static function createUnsavedDataObject($objectType) {
- switch ($objectType) {
- case 'collection':
- $json = [
- "name" => "Test"
- ];
- break;
-
- case 'item':
- // Convert to array
- $json = json_decode(json_encode(self::getItemTemplate("book")), true);
- break;
-
- case 'search':
- $json = [
- "name" => "Test",
- "conditions" => [
- [
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- ]
- ]
- ];
- break;
- }
- return $json;
- }
-
-
- public static function createDataObject($objectType, $format) {
- switch ($objectType) {
- case 'collection':
- return self::createCollection("Test", [], null, $format);
-
- case 'item':
- return self::createItem("book", [], null, $format);
-
- case 'search':
- return self::createSearch(
- "Test",
- [
- [
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- ]
- ],
- null,
- $format
- );
- }
- }
-
-
- //
- // Item modification methods
- //
- public static function getItemTemplate($itemType) {
- $response = self::get("items/new?itemType=$itemType");
- if ($response->getStatus() != 200) {
- var_dump($response->getStatus());
- var_dump($response->getBody());
- throw new Exception("Invalid response from template request");
- }
- return json_decode($response->getBody());
- }
-
-
- public static function createItem($itemType, $data=array(), $context=null, $returnFormat='responseJSON') {
- $json = self::getItemTemplate($itemType);
-
- if ($data) {
- foreach ($data as $field => $val) {
- $json->$field = $val;
- }
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- [
- "Content-Type: application/json",
- "Zotero-API-Key: " . self::$config['apiKey']
- ]
- );
-
- return self::handleCreateResponse('item', $response, $returnFormat, $context);
- }
-
-
- /**
- * POST a JSON item object to the main test user's account
- * and return the response
- */
- public static function postItem($json) {
- return self::postItems(array($json));
- }
-
-
- /**
- * POST a JSON items object to the main test user's account
- * and return the response
- */
- public static function postItems($json) {
- return self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- }
-
-
- public static function groupCreateItem($groupID, $itemType, $data=[], $context=null, $returnFormat='responseJSON') {
- $response = self::get("items/new?itemType=$itemType");
- $json = json_decode($response->getBody());
-
- if ($data) {
- foreach ($data as $field => $val) {
- $json->$field = $val;
- }
- }
-
- $response = self::groupPost(
- $groupID,
- "items?key=" . self::$config['apiKey'],
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- return self::handleCreateResponse('item', $response, $returnFormat, $context, $groupID);
- }
-
-
- public static function createAttachmentItem($linkMode, $data=[], $parentKey=false, $context=false, $returnFormat='responseJSON') {
- $response = self::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
- foreach ($data as $key => $val) {
- $json->{$key} = $val;
- }
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([$json]),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('item', $response, $returnFormat, $context);
- }
-
-
- public static function groupCreateAttachmentItem($groupID, $linkMode, $data=[], $parentKey=false, $context=false, $returnFormat='responseJSON') {
- $response = self::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
- foreach ($data as $key => $val) {
- $json->{$key} = $val;
- }
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::groupPost(
- $groupID,
- "items?key=" . self::$config['apiKey'],
- json_encode([$json]),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('item', $response, $returnFormat, $context, $groupID);
- }
-
-
- public static function createNoteItem($text="", $parentKey=false, $context=false, $returnFormat='responseJSON') {
- $response = self::get("items/new?itemType=note");
- $json = json_decode($response->getBody());
- $json->note = $text;
- if ($parentKey) {
- $json->parentItem = $parentKey;
- }
-
- $response = self::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- return self::handleCreateResponse('item', $response, $returnFormat, $context);
- }
-
-
- public static function createCollection($name, $data=array(), $context=null, $returnFormat='responseJSON') {
- if (is_array($data)) {
- $parent = isset($data['parentCollection']) ? $data['parentCollection'] : false;
- $relations = isset($data['relations']) ? $data['relations'] : new stdClass;
- }
- else {
- $parent = $data ? $data : false;
- $relations = new stdClass;
- }
-
- $json = [
- [
- 'name' => $name,
- 'parentCollection' => $parent,
- 'relations' => $relations
- ]
- ];
-
- $response = self::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('collection', $response, $returnFormat, $context);
- }
-
-
- public static function createSearch($name, $conditions=array(), $context=null, $returnFormat='responseJSON') {
- if ($conditions == 'default') {
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'test'
- )
- );
- }
-
- $json = [
- [
- 'name' => $name,
- 'conditions' => $conditions
- ]
- ];
-
- $response = self::userPost(
- self::$config['userID'],
- "searches?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- return self::handleCreateResponse('search', $response, $returnFormat, $context);
- }
-
-
- public static function getLibraryVersion() {
- $response = self::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- return $response->getHeader("Last-Modified-Version");
- }
-
-
- public static function getGroupLibraryVersion($groupID) {
- $response = self::groupGet(
- $groupID,
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- return $response->getHeader("Last-Modified-Version");
- }
-
-
- public static function getItem($keys, $context=null, $format=false, $groupID=false) {
- return self::getObject('item', $keys, $context, $format, $groupID);
- }
-
-
- public static function getItemResponse($keys, $context=null, $format=false, $groupID=false) {
- return self::getObjectResponse('item', $keys, $format, $context, $groupID);
- }
-
-
- public static function getCollection($keys, $context=null, $format=false, $groupID=false) {
- return self::getObject('collection', $keys, $context, $format, $groupID);
- }
-
- public static function getCollectionResponse($keys, $context=null, $format=false, $groupID=false) {
- return self::getObjectResponse('collection', $keys, $context, $format, $groupID);
- }
-
-
- public static function getSearch($keys, $context=null, $format=false, $groupID=false) {
- return self::getObject('search', $keys, $context, $format, $groupID);
- }
-
-
- public static function getSearchResponse($keys, $context=null, $format=false, $groupID=false) {
- return self::getObjectResponse('search', $keys, $context, $format, $groupID);
- }
-
-
- // Atom
- public static function getItemXML($keys, $context=null) {
- return self::getObject('item', $keys, $context, 'atom');
- }
-
-
- public static function getCollectionXML($keys, $context=null) {
- return self::getObject('collection', $keys, $context, 'atom');
- }
-
-
- public static function getSearchXML($keys, $context=null) {
- return self::getObject('search', $keys, $context, 'atom');
- }
-
-
- public static function groupGetItemXML($groupID, $keys, $context=null) {
- if (is_scalar($keys)) {
- $keys = array($keys);
- }
-
- $response = self::groupGet(
- $groupID,
- "items?key=" . self::$config['apiKey']
- . "&itemKey=" . implode(',', $keys) . "&order=itemKeyList"
- . "&content=json"
- );
- if ($context) {
- $context->assert200($response);
- }
- return self::getXMLFromResponse($response);
- }
-
-
- public static function getXMLFromFirstSuccessItem($response) {
- $key = self::getFirstSuccessKeyFromResponse($response);
- self::getItemXML($key);
- }
-
-
-
-
- //
- // HTTP methods
- //
- public static function get($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::get($url, $headers, $auth);
- if (self::$config['verbose'] >= 2) {
- echo "\n\n" . $response->getBody() . "\n";
- }
- return $response;
- }
-
- public static function superGet($url, $headers=[]) {
- return self::get(
- $url,
- $headers,
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
- public static function userGet($userID, $suffix, $headers=array(), $auth=false) {
- return self::get("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function groupGet($groupID, $suffix, $headers=array(), $auth=false) {
- return self::get("groups/$groupID/$suffix", $headers, $auth);
- }
-
- public static function post($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::post($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function superPost($url, $data, $headers=[]) {
- return self::post(
- $url,
- $data,
- $headers,
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
- public static function userPost($userID, $suffix, $data, $headers=array(), $auth=false) {
- return self::post("users/$userID/$suffix", $data, $headers, $auth);
- }
-
- public static function groupPost($groupID, $suffix, $data, $headers=array(), $auth=false) {
- return self::post("groups/$groupID/$suffix", $data, $headers, $auth);
- }
-
- public static function put($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::put($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function superPut($url, $data, $headers=[]) {
- return self::put(
- $url,
- $data,
- $headers,
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
- public static function userPut($userID, $suffix, $data, $headers=array(), $auth=false) {
- return self::put("users/$userID/$suffix", $data, $headers, $auth);
- }
-
- public static function groupPut($groupID, $suffix, $data, $headers=array(), $auth=false) {
- return self::put("groups/$groupID/$suffix", $data, $headers, $auth);
- }
-
- public static function patch($url, $data, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::patch($url, $data, $headers, $auth);
- return $response;
- }
-
- public static function userPatch($userID, $suffix, $data, $headers=array()) {
- return self::patch("users/$userID/$suffix", $data, $headers);
- }
-
- public static function head($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::head($url, $headers, $auth);
- return $response;
- }
-
- public static function userHead($userID, $suffix, $headers=array(), $auth=false) {
- return self::head("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function delete($url, $headers=array(), $auth=false) {
- $url = self::$config['apiURLPrefix'] . $url;
- if (self::$apiVersion) {
- $headers[] = "Zotero-API-Version: " . self::$apiVersion;
- }
- if (!$auth && self::$apiKey) {
- $headers[] = "Authorization: Bearer " . self::$apiKey;
- }
- $response = HTTP::delete($url, $headers, $auth);
- return $response;
- }
-
- public static function superDelete($url, $headers=[]) {
- return self::delete(
- $url,
- $headers,
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
- public static function userDelete($userID, $suffix, $headers=array(), $auth=false) {
- return self::delete("users/$userID/$suffix", $headers, $auth);
- }
-
- public static function groupDelete($groupID, $suffix, $headers=array(), $auth=false) {
- return self::delete("groups/$groupID/$suffix", $headers, $auth);
- }
-
-
- public static function userClear($userID) {
- $response = self::userPost(
- $userID,
- "clear",
- "",
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 204) {
- var_dump($response->getBody());
- throw new Exception("Error clearing user $userID");
- }
- }
-
- public static function groupClear($groupID) {
- $response = self::groupPost(
- $groupID,
- "clear",
- "",
- array(),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- if ($response->getStatus() != 204) {
- var_dump($response->getBody());
- throw new Exception("Error clearing group $groupID");
- }
- }
-
-
- //
- // Response parsing
- //
- public static function getXMLFromResponse($response) {
- try {
- $xml = new SimpleXMLElement($response->getBody());
- }
- catch (Exception $e) {
- var_dump($response->getBody());
- throw $e;
- }
- $xml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
- $xml->registerXPathNamespace('zapi', 'http://zotero.org/ns/api');
- return $xml;
- }
-
-
- public static function getJSONFromResponse($response, $asObject=false) {
- $json = json_decode($response->getBody(), !$asObject);
- if (is_null($json)) {
- var_dump($response->getBody());
- throw new Exception("JSON response could not be parsed");
- }
- return $json;
- }
-
-
- public static function getFirstSuccessKeyFromResponse($response) {
- $json = self::getJSONFromResponse($response);
- if (empty($json['success'])) {
- var_dump($response->getBody());
- throw new Exception("No success keys found in response");
- }
- return array_shift($json['success']);
- }
-
- public static function getSuccessfulKeysFromResponse($response) {
- $json = self::getJSONFromResponse($response);
- return array_map(function ($o) { return $o['key']; }, $json['successful']);
- }
-
-
- public static function parseDataFromAtomEntry($entryXML) {
- $key = (string) array_get_first($entryXML->xpath('//atom:entry/zapi:key'));
- $version = (string) array_get_first($entryXML->xpath('//atom:entry/zapi:version'));
- $content = array_get_first($entryXML->xpath('//atom:entry/atom:content'));
- if (is_null($content)) {
- var_dump($entryXML->asXML());
- throw new Exception("Atom response does not contain ");
- }
-
- // If 'content' contains XML, serialize all subnodes
- if ($content->count()) {
- $content = $content->asXML();
- }
- // Otherwise just get string content
- else {
- $content = (string) $content;
- }
-
- return array(
- "key" => $key,
- "version" => $version,
- "content" => $content
- );
- }
-
-
- public static function getContentFromResponse($response) {
- $xml = self::getXMLFromResponse($response);
- $data = self::parseDataFromAtomEntry($xml);
- return $data['content'];
- }
-
-
- /**
- * @return mixed Array (JSON) or SimpleXMLElement (HTML)
- */
- public static function getContentFromAtomResponse($response, $type = null) {
- $xml = self::getXMLFromResponse($response);
- $content = array_get_first($xml->xpath('//atom:entry/atom:content'));
- if (is_null($content)) {
- var_dump($xml->asXML());
- throw new Exception("Atom response does not contain ");
- }
-
- $subcontent = array_get_first($content->xpath('//zapi:subcontent'));
- if ($subcontent) {
- if (!$type) {
- throw new Exception('$type not provided for multi-content response');
- }
- switch ($type) {
- case 'json':
- return json_decode((string) $subcontent[0]->xpath('//zapi:subcontent[@zapi:type="json"]')[0], true);
-
- case 'html':
- $html = array_get_first($subcontent[0]->xpath('//zapi:subcontent[@zapi:type="html"]'));
- $html->registerXPathNamespace('html', 'http://www.w3.org/1999/xhtml');
- return $html;
-
- default:
- throw new Exception("Unknown data type '$type'");
- }
- }
- else {
- throw new Exception("Unimplemented");
- }
- }
-
-
- public static function parseLinkHeader($response) {
- $header = $response->getHeader('Link');
- $links = [];
- foreach (explode(',', $header) as $val) {
- preg_match('#<([^>]+)>; rel="([^"]+)"#', $val, $matches);
- $links[$matches[2]] = $matches[1];
- }
- return $links;
- }
-
-
- public static function resetKey($key) {
- $response = self::get(
- "keys/$key",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("GET returned " . $response->getStatus());
- }
-
- $json = self::getJSONFromResponse($response, true);
-
- $resetLibrary = function ($lib) {
- foreach ($lib as $permission => $value) {
- $lib->$permission = false;
- }
- };
- // Remove all individual library permissions and remove groups section
- if (isset($json->access->user)) {
- $resetLibrary($json->access->user);
- }
- unset($json->access->groups);
-
- $response = self::put(
- "users/" . self::$config['userID'] . "/keys/" . self::$config['apiKey'],
- json_encode($json),
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("PUT returned " . $response->getStatus());
- }
- }
-
-
- /**
- * @deprecated
- */
- public static function setKeyOption($userID, $key, $option, $val) {
- error_log("setKeyOption() is deprecated -- use setKeyUserPermission()");
-
- switch ($option) {
- case 'libraryNotes':
- $option = 'notes';
- break;
-
- case 'libraryWrite':
- $option = 'write';
- break;
- }
-
- self::setKeyUserPermission($key, $option, $val);
- }
-
-
- public static function setKeyUserPermission($key, $permission, $value) {
- $response = self::get(
- "keys/$key",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("GET returned " . $response->getStatus());
- }
-
- if (self::$apiVersion >= 3) {
- $json = self::getJSONFromResponse($response);
-
- switch ($permission) {
- case 'library':
- if (isset($json['access']['user']) && $value == !empty($json['access']['user']['library'])) {
- break;
- }
- $json['access']['user']['library'] = $value;
- break;
-
- case 'write':
- if (isset($json['access']['user']) && $value == !empty($json['access']['user']['write'])) {
- break;
- }
- $json['access']['user']['write'] = $value;
- break;
-
- case 'notes':
- if (isset($json['access']['user']) && $value == !empty($json['access']['user']['notes'])) {
- break;
- }
- $json['access']['user']['notes'] = $value;
- break;
- }
-
- $response = self::put(
- "keys/" . self::$config['apiKey'],
- json_encode($json),
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
- else {
- try {
- $xml = new SimpleXMLElement($response->getBody());
- }
- catch (Exception $e) {
- var_dump($response->getBody());
- throw $e;
- }
- foreach ($xml->access as $access) {
- switch ($permission) {
- case 'library':
- $current = (int) $access['library'];
- if ($current != $value) {
- $access['library'] = (int) $value;
- }
- break;
-
- case 'write':
- if (!isset($access['library'])) {
- continue;
- }
- $current = (int) $access['write'];
- if ($current != $value) {
- $access['write'] = (int) $value;
- }
- break;
-
- case 'notes':
- if (!isset($access['library'])) {
- break;
- }
- $current = (int) $access['notes'];
- if ($current != $value) {
- $access['notes'] = (int) $value;
- }
- break;
- }
- }
-
- $response = self::put(
- "keys/" . self::$config['apiKey'],
- $xml->asXML(),
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("PUT returned " . $response->getStatus());
- }
- }
-
-
- public static function setKeyGroupPermission($key, $groupID, $permission, $value) {
- $response = self::get(
- "keys/$key",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("GET returned " . $response->getStatus());
- }
-
- $json = self::getJSONFromResponse($response);
- if (!isset($json['access'])) {
- $json['access'] = [];
- }
- if (!isset($json['access']['groups'])) {
- $json['access']['groups'] = [];
- }
- $json['access']['groups'][$groupID][$permission] = true;
- $response = self::put(
- "keys/" . $key,
- json_encode($json),
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- if ($response->getStatus() != 200) {
- var_dump($response->getBody());
- throw new Exception("PUT returned " . $response->getStatus());
- }
- }
-
-
- public static function getPluralObjectType($objectType) {
- if ($objectType == 'search') {
- return $objectType . "es";
- }
- return $objectType . "s";
- }
-
-
- private static function getObjectResponse($objectType, $keys, $context=null, $format=false, $groupID=false) {
- $objectTypePlural = self::getPluralObjectType($objectType);
-
- $single = is_string($keys);
-
- $url = "$objectTypePlural";
- if ($single) {
- $url .= "/$keys";
- }
- $url .= "?key=" . self::$config['apiKey'];
- if (!$single) {
- $url .= "&{$objectType}Key=" . implode(',', $keys) . "&order={$objectType}KeyList";
- }
- if ($format !== false) {
- $url .= "&format=" . $format;
- if ($format == 'atom') {
- $url .= '&content=json';
- }
- }
- if ($groupID) {
- $response = self::groupGet($groupID, $url);
- }
- else {
- $response = self::userGet(self::$config['userID'], $url);
- }
- if ($context) {
- $context->assert200($response);
- }
- return $response;
- }
-
-
- private static function getObject($objectType, $keys, $context=null, $format=false, $groupID=false) {
- $response = self::getObjectResponse($objectType, $keys, $context, $format, $groupID);
- $contentType = $response->getHeader('Content-Type');
- switch ($contentType) {
- case 'application/json':
- return self::getJSONFromResponse($response);
-
- case 'application/atom+xml':
- return self::getXMLFromResponse($response);
-
- default:
- var_dump($response->getBody());
- throw new Exception("Unknown content type '$contentType'");
- }
- }
-
-
- private static function handleCreateResponse($objectType, $response, $returnFormat, $context=null, $groupID=false) {
- if ($context) {
- if (!preg_match('/APIv([0-9]+)/', get_class($context), $matches)) {
- throw new Exception("Unexpected namespace");
- }
- $apiVersion = (int) $matches[1];
- }
-
- $uctype = ucwords($objectType);
-
- if ($context) {
- $context->assert200($response);
- }
-
- if ($returnFormat == 'response') {
- return $response;
- }
-
- $json = self::getJSONFromResponse($response);
-
- if ($returnFormat != 'responseJSON' && sizeOf($json['success']) != 1) {
- var_dump($json);
- throw new Exception("$uctype creation failed");
- }
-
- if ($returnFormat == 'responseJSON') {
- return $json;
- }
-
- $key = array_shift($json['success']);
-
- if ($returnFormat == 'key') {
- return $key;
- }
-
- // returnFormat can be 'json', 'jsonResponse', 'atom', 'atomResponse', 'content', 'data'
- $asResponse = false;
- if (preg_match('/response$/i', $returnFormat)) {
- $returnFormat = substr($returnFormat, 0, -8);
- $asResponse = true;
- }
- $func = 'get' . $uctype . ($asResponse ? 'Response' : '');
-
- if (substr($returnFormat, 0, 4) == 'json') {
- $response = self::$func($key, $context, 'json', $groupID);
- if ($returnFormat == 'json' || $returnFormat == 'jsonResponse') {
- return $response;
- }
- if ($returnFormat == 'jsonData') {
- return $response['data'];
- }
- }
-
- // Request Atom
- $response = self::$func($key, $context, 'atom', $groupID);
-
- if ($returnFormat == 'atom' || $returnFormat == 'atomResponse') {
- return $response;
- }
-
- $xml = self::getXMLFromResponse($response);
- $data = self::parseDataFromAtomEntry($xml);
-
- if ($returnFormat == 'data') {
- return $data;
- }
- if ($returnFormat == 'content') {
- return $data['content'];
- }
- if ($returnFormat == 'atomJSON') {
- return json_decode($data['content'], true);
- }
-
- throw new Exception("Invalid result format '$returnFormat'");
- }
-}
-
-API3::loadConfig();
diff --git a/tests/remote/include/bootstrap.inc.php b/tests/remote/include/bootstrap.inc.php
deleted file mode 100644
index de8398e1..00000000
--- a/tests/remote/include/bootstrap.inc.php
+++ /dev/null
@@ -1,64 +0,0 @@
- $config['awsRegion'],
- 'version' => 'latest'
-];
-// Access key and secret (otherwise uses IAM role authentication)
-if (!empty($config['awsAccessKey'])) {
- $awsConfig['credentials'] = [
- 'key' => $config['awsAccessKey'],
- 'secret' => $config['awsSecretKey']
- ];
-}
-Z_Tests::$AWS = new Aws\Sdk($awsConfig);
-unset($awsConfig);
-
-// Wipe data and create API key
-require_once 'http.inc.php';
-$response = HTTP::post(
- $config['apiURLPrefix'] . "test/setup?u=" . $config['userID'],
- " ",
- [],
- [
- "username" => $config['rootUsername'],
- "password" => $config['rootPassword']
- ]
-);
-$json = json_decode($response->getBody());
-if (!$json) {
- echo $response->getStatus() . "\n\n";
- echo $response->getBody();
- throw new Exception("Invalid test setup response");
-}
-$config['apiKey'] = $json->apiKey;
-\Zotero\Tests\Config::update($config);
-
-// Set up groups
-require 'groups.inc.php';
-
-/**
- * @param $arr
- * @return mixed
- */
-function array_get_first($arr) {
- if (is_array($arr) && isset($arr[0])) {
- return $arr[0];
- }
- return null;
-}
diff --git a/tests/remote/include/config.inc.php b/tests/remote/include/config.inc.php
deleted file mode 100644
index 23a1459e..00000000
--- a/tests/remote/include/config.inc.php
+++ /dev/null
@@ -1,38 +0,0 @@
-
-namespace Zotero\Tests;
-
-if (!class_exists('Zotero\Tests\Config')) {
- class Config {
- private static $instance;
- private static $config;
-
- protected function __construct() {
- self::$config = parse_ini_file('config.ini');
- }
-
- private static function getInstance() {
- if (!isset(self::$instance)) {
- self::$instance = new static();
- }
- return self::$instance;
- }
-
- public static function getConfig() {
- $instance = self::getInstance();
- return $instance::$config;
- }
-
- public static function update($config) {
- self::getInstance();
-
- foreach ($config as $key => $val) {
- if (!isset(self::$config[$key]) || self::$config[$key] !== $val) {
- self::$config[$key] = $val;
- }
- }
- }
- }
-}
-
-$config = Config::getConfig();
-?>
diff --git a/tests/remote/include/config.ini-sample b/tests/remote/include/config.ini-sample
deleted file mode 100644
index e02e5dd5..00000000
--- a/tests/remote/include/config.ini-sample
+++ /dev/null
@@ -1,31 +0,0 @@
-; 0 => off, 1 => HTTP requests, 2 => HTTP request/response bodies
-verbose = 0
-syncURLPrefix = ""
-apiURLPrefix = ""
-rootUsername = ""
-rootPassword = ""
-awsRegion = "us-east-1"
-s3Bucket = ""
-; Leave credentials empty to use IAM role
-awsAccessKey = ""
-awsSecretKey = ""
-syncVersion = 9
-
-[user1]
-userID = 0
-libraryID = 0
-username = "phpunit"
-password = ""
-; Should have libraryEditing='members'
-ownedPrivateGroupID = 0
-ownedPrivateGroupLibraryID = 0
-ownedPrivateGroupName = "Private Test Group"
-
-; Should be in user 1's private group
-[user2]
-userID2 = 0
-username2 = "phpunit2"
-password2 = ""
-; User 1 should not be a member
-ownedPrivateGroupID2 = 0
-ownedPrivateGroupLibraryID2 = 0
diff --git a/tests/remote/include/groups.inc.php b/tests/remote/include/groups.inc.php
deleted file mode 100644
index f372d6e9..00000000
--- a/tests/remote/include/groups.inc.php
+++ /dev/null
@@ -1,73 +0,0 @@
- $config['userID'],
- 'type' => 'PublicOpen',
- 'libraryReading' => 'all'
- ]);
-}
-if (!$config['ownedPublicNoAnonymousGroupID']) {
- $config['ownedPublicNoAnonymousGroupID'] = API3::createGroup([
- 'owner' => $config['userID'],
- 'type' => 'PublicClosed',
- 'libraryReading' => 'members'
- ]);
-}
-foreach ($toDelete as $groupID) {
- API3::deleteGroup($groupID);
-}
-
-$config['numOwnedGroups'] = 3;
-$config['numPublicGroups'] = 2;
-
-foreach ($groups as $group) {
- if (!in_array($group['id'], $toDelete)) {
- API3::groupClear($group['id']);
- }
-}
-
-\Zotero\Tests\Config::update($config);
-
-unset($response);
-unset($groups);
-unset($toDelete);
diff --git a/tests/remote/include/http.inc.php b/tests/remote/include/http.inc.php
deleted file mode 100644
index 16246749..00000000
--- a/tests/remote/include/http.inc.php
+++ /dev/null
@@ -1,141 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once 'HTTP/Request2.php';
-
-class HTTP {
- private static $config;
-
- private static function loadConfig() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- }
-
-
- private static function getRequest($url, $headers, $auth) {
- $req = new HTTP_Request2($url);
- $req->setHeader($headers);
- if ($auth) {
- $req->setAuth($auth['username'], $auth['password']);
- }
- $req->setHeader("Expect:");
- $req->setConfig([
- 'ssl_verify_peer' => false,
- 'ssl_verify_host' => false
- ]);
-
- return $req;
- }
-
- private static function sendRequest($req) {
- return $req->send();
- }
-
-
- public static function get($url, $headers=array(), $auth=false) {
- self::loadConfig();
- $req = self::getRequest($url, $headers, $auth);
- if (self::$config['verbose'] >= 1) {
- echo "\nGET $url\n";
- }
- $response = self::sendRequest($req);
- if (self::$config['verbose'] >= 2) {
- echo "\n\n" . $response->getBody() . "\n";
- }
- return $response;
- }
-
- public static function post($url, $data, $headers=array(), $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod(HTTP_Request2::METHOD_POST);
- if (is_array($data)) {
- $req->addPostParameter($data);
- }
- else {
- $req->setBody($data);
- }
- if (self::$config['verbose'] >= 1) {
- echo "\nPOST $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-
- public static function put($url, $data, $headers=array(), $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod(HTTP_Request2::METHOD_PUT);
- $req->setBody($data);
- if (self::$config['verbose'] >= 1) {
- echo "\nPUT $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-
- public static function patch($url, $data, $headers=array(), $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod("PATCH");
- $req->setBody($data);
- if (self::$config['verbose'] >= 1) {
- echo "\nPATCH $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-
- public static function head($url, $headers=array(), $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod(HTTP_Request2::METHOD_HEAD);
- if (self::$config['verbose'] >= 1) {
- echo "\nHEAD $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-
-
- public static function options($url, $headers=[], $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod(HTTP_Request2::METHOD_OPTIONS);
- if (self::$config['verbose'] >= 1) {
- echo "\nOPTIONS $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-
- public static function delete($url, $headers=array(), $auth=false) {
- $req = self::getRequest($url, $headers, $auth);
- $req->setMethod(HTTP_Request2::METHOD_DELETE);
- if (self::$config['verbose'] >= 1) {
- echo "\nDELETE $url\n";
- }
- $response = self::sendRequest($req);
- return $response;
- }
-}
diff --git a/tests/remote/include/sync.inc.php b/tests/remote/include/sync.inc.php
deleted file mode 100644
index 7d6851f3..00000000
--- a/tests/remote/include/sync.inc.php
+++ /dev/null
@@ -1,401 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once 'include/bootstrap.inc.php';
-require_once '../../model/Utilities.inc.php';
-
-class Sync {
- private static $config;
-
- public static function loadConfig() {
- if (self::$config) {
- return;
- }
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- date_default_timezone_set('UTC');
- }
-
-
- public static function useZoteroVersion($version=false) {
- if ($version) {
- self::$config['zoteroVersion'] = $version;
- }
- else {
- self::$config['zoteroVersion'] = null;
- }
- }
-
-
- public static function createItem($sessionID, $libraryID, $itemType, $data=array(), $context) {
- $xml = Sync::updated($sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $key = Zotero_Utilities::randomString(8, 'key', true);
- $dateAdded = date( 'Y-m-d H:i:s', time() - 1);
- $dateModified = date( 'Y-m-d H:i:s', time());
-
- $xmlstr = ''
- . ''
- . '- ';
- if ($data) {
- $relatedstr = "";
- foreach ($data as $field => $val) {
- $xmlstr .= '
' . $val . ' ';
- if ($key == 'related') {
- $relatedstr .= "$val ";
- }
- }
- $xmlstr .= $relatedstr;
- }
- $xmlstr .= ' '
- . ' '
- . '';
- $response = Sync::upload($sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload($sessionID, $response, $context);
-
- return $key;
- }
-
-
- public static function deleteItem($sessionID, $libraryID, $itemKey, $context=null) {
- $xml = Sync::updated($sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $xmlstr = ''
- . ''
- . ''
- . ' '
- . ' '
- . ' '
- . '';
- $response = Sync::upload($sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload($sessionID, $response, $context);
- }
-
-
- public static function createCollection($sessionID, $libraryID, $name, $parent, $context) {
- $xml = Sync::updated($sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $key = Zotero_Utilities::randomString(8, 'key', true);
- $dateAdded = date( 'Y-m-d H:i:s', time() - 1);
- $dateModified = date( 'Y-m-d H:i:s', time());
-
- $xmlstr = ''
- . ''
- . ' '
- . ' '
- . '';
- $response = Sync::upload($sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload($sessionID, $response, $context);
-
- return $key;
- }
-
-
- public static function createSearch($sessionID, $libraryID, $name, $conditions, $context) {
- if ($conditions == 'default') {
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'test'
- )
- );
- }
-
- $xml = Sync::updated($sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $key = Zotero_Utilities::randomString(8, 'key', true);
- $dateAdded = date( 'Y-m-d H:i:s', time() - 1);
- $dateModified = date( 'Y-m-d H:i:s', time());
-
- $xmlstr = ''
- . ''
- . '';
- $i = 1;
- foreach ($conditions as $condition) {
- $xmlstr .= ' ';
- $i++;
- }
- $xmlstr .= ' '
- . ' '
- . '';
- $response = Sync::upload($sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload($sessionID, $response, $context);
-
- return $key;
- }
-
-
- //
- // Sync operations
- //
- public static function login($credentials=false) {
- if (!$credentials) {
- $credentials['username'] = self::$config['username'];
- $credentials['password'] = self::$config['password'];
- }
-
- $url = self::$config['syncURLPrefix'] . "login";
- $response = HTTP::post(
- $url,
- array(
- "version" => self::$config['syncVersion'],
- "username" => $credentials['username'],
- "password" => $credentials['password']
- )
- );
- self::checkResponse($response);
- $xml = new SimpleXMLElement($response->getBody());
- $sessionID = (string) $xml->sessionID;
- self::checkSessionID($sessionID);
- return $sessionID;
- }
-
-
- public static function updated($sessionID, $lastsync=1, $allowError=false, $allowQueued=false, $params=array()) {
- $response = self::req($sessionID, "updated", array_merge(array("lastsync" => $lastsync), $params));
- $xml = Sync::getXMLFromResponse($response);
-
- if (isset($xml->updated) || (isset($xml->error) && $allowError)
- || (isset($xml->locked) && $allowQueued)) {
- return $xml;
- }
-
- if (!isset($xml->locked)) {
- var_dump($xml->asXML());
- throw new Exception("Not locked");
- }
-
- $max = 5;
- $try = 0;
- do {
- $wait = (int) $xml->locked['wait'];
- sleep($wait / 1000);
-
- $xml = Sync::updated($sessionID, $lastsync, $allowError, true, $params);
-
- $try++;
- }
- while (isset($xml->locked) && $try < $max);
-
- if (isset($xml->locked)) {
- throw new Exception("Download did not finish after $try attempts");
- }
-
- if (!$allowError && !isset($xml->updated)) {
- var_dump($xml->asXML());
- throw new Exception(" not found");
- }
-
- return $xml;
- }
-
-
- public static function upload($sessionID, $updateKey, $data, $allowError=false) {
- return self::req(
- $sessionID,
- "upload",
- array(
- "updateKey" => $updateKey,
- "data" => $data,
- ),
- true,
- $allowError
- );
- }
-
-
- public static function uploadstatus($sessionID, $allowError=false) {
- return self::req($sessionID, "uploadstatus", false, false, true);
- }
-
-
- public static function waitForUpload($sessionID, $response, $context, $allowError=false) {
- $xml = Sync::getXMLFromResponse($response);
-
- if (isset($xml->uploaded) || (isset($xml->error) && $allowError)) {
- return $xml;
- }
-
- $context->assertTrue(isset($xml->queued));
-
- $max = 5;
- do {
- $wait = (int) $xml->queued['wait'];
- sleep($wait / 1000);
-
- $response = Sync::uploadStatus($sessionID, $allowError);
- $xml = Sync::getXMLFromResponse($response);
-
- $max--;
- }
- while (isset($xml->queued) && $max > 0);
-
- if (!$max) {
- $context->fail("Upload did not finish after $max attempts");
- }
-
- if (!$allowError) {
- $context->assertTrue(isset($xml->uploaded));
- }
-
- return $xml;
- }
-
-
- public static function logout($sessionID) {
- $url = self::$config['syncURLPrefix'] . "logout";
- $response = HTTP::post(
- $url,
- array(
- "version" => self::$config['syncVersion'],
- "sessionid" => $sessionID
- )
- );
- self::checkResponse($response);
- $xml = new SimpleXMLElement($response->getBody());
- if (!$xml->loggedout) {
- throw new Exception("Error logging out");
- }
- }
-
-
- public static function checkResponse($response, $allowError=false) {
- $responseText = $response->getBody();
-
- if (empty($responseText)) {
- throw new Exception("Response is empty");
- }
-
- $domdoc = new DOMDocument;
- try {
- $domdoc->loadXML($responseText);
- }
- catch (Exception $e) {
- var_dump($responseText);
- throw ($e);
- }
- if ($domdoc->firstChild->tagName != "response") {
- throw new Exception("Invalid XML output: " . $responseText);
- }
-
- if (!$allowError && $domdoc->firstChild->firstChild->tagName == "error") {
- if ($domdoc->firstChild->firstChild->getAttribute('code') == "INVALID_LOGIN") {
- throw new Exception("Invalid login");
- }
-
- throw new Exception($responseText);
- }
- }
-
-
- public static function getXMLFromResponse($response) {
- try {
- $xml = new SimpleXMLElement($response->getBody());
- }
- catch (Exception $e) {
- var_dump($response->getBody());
- throw $e;
- }
- $xml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
- $xml->registerXPathNamespace('zapi', 'http://zotero.org/ns/api');
- return $xml;
- }
-
-
-
- private static function req($sessionID, $path, $params=array(), $gzip=false, $allowError=false) {
- $url = self::$config['syncURLPrefix'] . $path;
-
- $params = array_merge(
- array(
- "sessionid" => $sessionID,
- "version" => self::$config['syncVersion']
- ),
- $params ? $params : array()
- );
-
- if ($gzip) {
- $data = "";
- foreach ($params as $key => $val) {
- $data .= $key . "=" . urlencode($val) . "&";
- }
- $data = gzdeflate(substr($data, 0, -1));
- $headers = [
- "Content-Type: application/octet-stream",
- "Content-Encoding: gzip"
- ];
- }
- else {
- $data = $params;
- $headers = [];
-
- }
-
- if (!empty(self::$config['zoteroVersion'])) {
- $headers[] = "X-Zotero-Version: " . self::$config['zoteroVersion'];
- }
-
- $response = HTTP::post($url, $data, $headers);
- self::checkResponse($response, $allowError);
- return $response;
- }
-
-
- private static function checkSessionID($sessionID) {
- if (!preg_match('/^[a-g0-9]{32}$/', $sessionID)) {
- throw new Exception("Invalid session id");
- }
- }
-}
-
-Sync::loadConfig();
diff --git a/tests/remote/tests/API/1/APITests.inc.php b/tests/remote/tests/API/1/APITests.inc.php
deleted file mode 100644
index f064a128..00000000
--- a/tests/remote/tests/API/1/APITests.inc.php
+++ /dev/null
@@ -1,162 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv1;
-require_once 'tests/API/APITests.inc.php';
-use \API2 as API, \Exception, \SimpleXMLElement;
-require_once 'include/api2.inc.php';
-
-//
-// Helper functions
-//
-class APITests extends \APITests {
- protected static $config;
- protected static $nsZAPI;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
-
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- self::$nsZAPI = 'http://zotero.org/ns/api';
-
- API::useAPIVersion(1);
-
- // Enable note access
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 1
- );
- }
-
-
- public function setUp() {
- parent::setUp();
- API::useAPIVersion(1);
- }
-
-
- public function test() {}
-
- public function __call($name, $arguments) {
- if (preg_match("/^assert([1-5][0-9]{2})$/", $name, $matches)) {
- $this->assertHTTPStatus($matches[1], $arguments[0]);
- // Check response body
- if (isset($arguments[1])) {
- $this->assertEquals($arguments[1], $arguments[0]->getBody());
- }
- return;
- }
- // assertNNNForObject($response, $message=false, $pos=0)
- if (preg_match("/^assert([1-5][0-9]{2}|Unchanged)ForObject$/", $name, $matches)) {
- $code = $matches[1];
- if ($arguments[0] instanceof \HTTP_Request2_Response) {
- $this->assert200($arguments[0]);
- $json = json_decode($arguments[0]->getBody(), true);
- }
- else if (is_string($arguments[0])) {
- $json = json_decode($arguments[0], true);
- }
- else {
- $json = $arguments[0];
- }
- $this->assertNotNull($json);
-
- $expectedMessage = !empty($arguments[1]) ? $arguments[1] : false;
- $index = isset($arguments[2]) ? $arguments[2] : 0;
-
- if ($code == 200) {
- $this->assertArrayHasKey('success', $json);
- if (!isset($json['success'][$index])) {
- var_dump($json);
- throw new Exception("Index $index not found in success object");
- }
- if ($expectedMessage) {
- throw new Exception("Cannot check response message of object for HTTP $code");
- }
- }
- else if ($code == 'Unchanged') {
- $this->assertArrayHasKey('unchanged', $json);
- $this->assertArrayHasKey($index, $json['unchanged']);
- if ($expectedMessage) {
- throw new Exception("Cannot check response message of unchanged object");
- }
- }
- else if ($code[0] == '4' || $code[0] == '5') {
- $this->assertArrayHasKey('failed', $json);
- $this->assertArrayHasKey($index, $json['failed']);
- $this->assertEquals($code, $json['failed'][$index]['code']);
- if ($expectedMessage) {
- $this->assertEquals($expectedMessage, $json['failed'][$index]['message']);
- }
- }
- else {
- throw new Exception("HTTP $code cannot be returned for an individual object");
- }
- return;
- }
- throw new Exception("Invalid function $name");
- }
-
-
- protected function assertHasResults($res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertNotEquals(0, (int) $zapiNodes->totalResults);
- $this->assertNotEquals(0, count($xml->entry));
- }
-
-
- protected function assertNumResults($num, $res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $this->assertEquals($num, count($xml->entry));
- }
-
- protected function assertTotalResults($num, $res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertEquals($num, (int) $zapiNodes->totalResults);
- }
-
-
- protected function assertNoResults($res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertEquals(1, count($zapiNodes->totalResults));
- $this->assertEquals(0, (int) $zapiNodes->totalResults);
- $this->assertEquals(0, count($xml->entry));
- }
-}
-
diff --git a/tests/remote/tests/API/1/CollectionTest.php b/tests/remote/tests/API/1/CollectionTest.php
deleted file mode 100644
index 33629abc..00000000
--- a/tests/remote/tests/API/1/CollectionTest.php
+++ /dev/null
@@ -1,170 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv1;
-use \API2 AS API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class CollectionTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewSingleCollection() {
- $name = "Test Collection";
-
- $json = array(
- 'name' => $name,
- 'parent' => false
- );
-
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
-
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
- $this->assertEquals(0, (int) array_get_first($xml->xpath('/atom:feed/zapi:numCollections')));
-
- $data = API::parseDataFromAtomEntry($xml);
-
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
-
- return $data;
- }
-
-
- /**
- * @depends testNewSingleCollection
- */
- public function testNewSingleSubcollection($data) {
- $name = "Test Subcollection";
- $parent = $data['key'];
-
- $json = array(
- 'name' => $name,
- 'parent' => $parent
- );
-
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
-
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $data = API::parseDataFromAtomEntry($xml);
-
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
- $this->assertEquals($parent, (string) $json->parent);
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$parent?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:entry/zapi:numCollections')));
- }
-
-
- public function testNewSingleCollectionWithoutParentProperty() {
- $name = "Test Collection";
-
- $json = array(
- 'name' => $name
- );
-
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
- }
-
-
- public function testEditSingleCollection() {
- API::useAPIVersion(2);
- $xml = API::createCollection("Test", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- $version = $data['version'];
- API::useAPIVersion(1);
-
- $xml = API::getCollectionXML($data['key']);
- $etag = (string) array_get_first($xml->xpath('//atom:entry/atom:content/@etag'));
- $this->assertNotNull($etag);
-
- $newName = "Test 2";
- $json = array(
- "name" => $newName,
- "parent" => false
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "collections/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Match: $etag"
- )
- );
- $this->assert200($response);
-
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($newName, (string) $json->name);
- }
-}
diff --git a/tests/remote/tests/API/1/ItemTest.php b/tests/remote/tests/API/1/ItemTest.php
deleted file mode 100644
index b441216c..00000000
--- a/tests/remote/tests/API/1/ItemTest.php
+++ /dev/null
@@ -1,66 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv1;
-use \API2 AS API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class ItemTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function testCreateItemWithChildren() {
- $json = API::getItemTemplate("newspaperArticle");
- $noteJSON = API::getItemTemplate("note");
- $noteJSON->note = "Here's a test note
";
- $json->notes = array(
- $noteJSON
- );
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- ))
- );
- $this->assert201($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertNumResults(1, $response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numChildren')));
- }
-}
diff --git a/tests/remote/tests/API/1/TranslationTest.php b/tests/remote/tests/API/1/TranslationTest.php
deleted file mode 100644
index f27f691e..00000000
--- a/tests/remote/tests/API/1/TranslationTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv1;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class TranslationTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- /**
- * @group translation
- */
- public function testWebTranslationSingle() {
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/Zotero-Guide-Librarians-Researchers-Educators/dp/0838985890/"
- ]),
- array("Content-Type: application/json")
- );
- $this->assert201($response);
- $xml = API::getXMLFromResponse($response);
- $json = json_decode(API::parseDataFromAtomEntry($xml)['content']);
- $this->assertEquals('Zotero: A Guide for Librarians, Researchers and Educators', $json->title);
- }
-
-
- public function testWebTranslationMultiple() {
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians"
- ]),
- array("Content-Type: application/json")
- );
- $this->assert300($response);
- $json = json_decode($response->getBody());
- $results = get_object_vars($json);
-
- $key = array_keys($results)[0];
- $val = array_values($results)[0];
-
- $this->assertEquals(0, strpos($key, 'http'));
- $this->assertEquals('Zotero: A guide for librarians, researchers, and educators, Second Edition', $val);
-
- // Can't test posting on v1, because generated token isn't returned
- }
-}
diff --git a/tests/remote/tests/API/2/APITests.inc.php b/tests/remote/tests/API/2/APITests.inc.php
deleted file mode 100644
index 62016502..00000000
--- a/tests/remote/tests/API/2/APITests.inc.php
+++ /dev/null
@@ -1,180 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-require_once 'tests/API/APITests.inc.php';
-use API2 as API, Exception, SimpleXMLElement;
-require_once 'include/api2.inc.php';
-
-//
-// Helper functions
-//
-class APITests extends \APITests {
- protected static $config;
- protected static $nsZAPI;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
-
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- self::$nsZAPI = 'http://zotero.org/ns/api';
-
- API::useAPIVersion(2);
-
- // Enable note access
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 1
- );
- }
-
-
- public function setUp() {
- parent::setUp();
- API::useAPIVersion(2);
- }
-
-
- public function test() {}
-
- public function __call($name, $arguments) {
- if (preg_match("/^assert([1-5][0-9]{2})$/", $name, $matches)) {
- $this->assertHTTPStatus($matches[1], $arguments[0]);
- // Check response body
- if (isset($arguments[1])) {
- $this->assertEquals($arguments[1], $arguments[0]->getBody());
- }
- return;
- }
- // assertNNNForObject($response, $message=false, $pos=0)
- if (preg_match("/^assert([1-5][0-9]{2}|Unchanged)ForObject$/", $name, $matches)) {
- $code = $matches[1];
- if ($arguments[0] instanceof \HTTP_Request2_Response) {
- $this->assert200($arguments[0]);
- $json = json_decode($arguments[0]->getBody(), true);
- }
- else if (is_string($arguments[0])) {
- $json = json_decode($arguments[0], true);
- }
- else {
- $json = $arguments[0];
- }
- $this->assertNotNull($json);
-
- $expectedMessage = !empty($arguments[1]) ? $arguments[1] : false;
- $index = isset($arguments[2]) ? $arguments[2] : 0;
-
- if ($code == 200) {
- try {
- $this->assertArrayHasKey('success', $json);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- if (!isset($json['success'][$index])) {
- var_dump($json);
- throw new Exception("Index $index not found in success object");
- }
- if ($expectedMessage) {
- throw new Exception("Cannot check response message of object for HTTP $code");
- }
- }
- else if ($code == 'Unchanged') {
- try {
- $this->assertArrayHasKey('unchanged', $json);
- $this->assertArrayHasKey($index, $json['unchanged']);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- if ($expectedMessage) {
- throw new Exception("Cannot check response message of unchanged object");
- }
- }
- else if ($code[0] == '4' || $code[0] == '5') {
- try {
- $this->assertArrayHasKey('failed', $json);
- $this->assertArrayHasKey($index, $json['failed']);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- $this->assertEquals($code, $json['failed'][$index]['code']);
- if ($expectedMessage) {
- $this->assertEquals($expectedMessage, $json['failed'][$index]['message']);
- }
- }
- else {
- throw new Exception("HTTP $code cannot be returned for an individual object");
- }
- return;
- }
- throw new Exception("Invalid function $name");
- }
-
-
- protected function assertHasResults($res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertNotEquals(0, (int) $zapiNodes->totalResults);
- $this->assertNotEquals(0, count($xml->entry));
- }
-
-
- protected function assertNumResults($num, $res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $this->assertEquals($num, count($xml->entry));
- }
-
- protected function assertTotalResults($num, $res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertEquals($num, (int) $zapiNodes->totalResults);
- }
-
-
- protected function assertNoResults($res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertEquals(1, count($zapiNodes->totalResults));
- $this->assertEquals(0, (int) $zapiNodes->totalResults);
- $this->assertEquals(0, count($xml->entry));
- }
-}
-
diff --git a/tests/remote/tests/API/2/AtomTest.php b/tests/remote/tests/API/2/AtomTest.php
deleted file mode 100644
index cb92a8b6..00000000
--- a/tests/remote/tests/API/2/AtomTest.php
+++ /dev/null
@@ -1,140 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class AtomTests extends APITests {
- private static $items;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- // Create test data
- $key = API::createItem("book", array(
- "title" => "Title",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- )
- )
- ), null, 'key');
- self::$items[$key] = 'Last, First. Title, n.d. '
- . \Zotero_Utilities::formatJSON(json_decode('{"itemKey":"","itemVersion":0,"itemType":"book","title":"Title","creators":[{"creatorType":"author","firstName":"First","lastName":"Last"}],"abstractNote":"","series":"","seriesNumber":"","volume":"","numberOfVolumes":"","edition":"","place":"","publisher":"","date":"","numPages":"","language":"","ISBN":"","shortTitle":"","url":"","accessDate":"","archive":"","archiveLocation":"","libraryCatalog":"","callNumber":"","rights":"","extra":"","tags":[],"collections":[],"relations":{}}'))
- . ' ';
-
- $key = API::createItem("book", array(
- "title" => "Title 2",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- ), null, 'key');
- self::$items[$key] = 'Last, First. Title 2. Edited by Ed McEditor, n.d. '
- . \Zotero_Utilities::formatJSON(json_decode('{"itemKey":"","itemVersion":0,"itemType":"book","title":"Title 2","creators":[{"creatorType":"author","firstName":"First","lastName":"Last"},{"creatorType":"editor","firstName":"Ed","lastName":"McEditor"}],"abstractNote":"","series":"","seriesNumber":"","volume":"","numberOfVolumes":"","edition":"","place":"","publisher":"","date":"","numPages":"","language":"","ISBN":"","shortTitle":"","url":"","accessDate":"","archive":"","archiveLocation":"","libraryCatalog":"","callNumber":"","rights":"","extra":"","tags":[],"collections":[],"relations":{}}'))
- . ' ';
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testFeedURIs() {
- $userID = self::$config['userID'];
-
- $response = API::userGet(
- $userID,
- "items?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $links = $xml->xpath('/atom:feed/atom:link');
- $this->assertEquals(self::$config['apiURLPrefix'] . "users/$userID/items", (string) $links[0]['href']);
-
- // 'order'/'sort' should stay as-is, not turn into 'sort'/'direction'
- $response = API::userGet(
- $userID,
- "items?key=" . self::$config['apiKey'] . '&order=dateModified&sort=asc'
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $links = $xml->xpath('/atom:feed/atom:link');
- $this->assertEquals(self::$config['apiURLPrefix'] . "users/$userID/items?order=dateModified&sort=asc", (string) $links[0]['href']);
- }
-
-
- public function testMultiContent() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey']
- . "&itemKey=$keyStr&content=bib,json"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(sizeOf($keys), (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add namespace prefix (from )
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key], $content);
- }
- }
-
-
- public function testMultiContentCached() {
- self::testMultiContent();
- }
-}
-?>
diff --git a/tests/remote/tests/API/2/BibTest.php b/tests/remote/tests/API/2/BibTest.php
deleted file mode 100644
index ac88046e..00000000
--- a/tests/remote/tests/API/2/BibTest.php
+++ /dev/null
@@ -1,186 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class BibTests extends APITests {
- private static $items;
- private static $styles = array("default", "apa");
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- // Create test data
- $key = API::createItem("book", array(
- "title" => "Title",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- )
- )
- ), null, 'key');
- self::$items[$key] = array(
- "citation" => array(
- "default" => 'Last, Title. ',
- "apa" => '(Last, n.d.) '
- ),
- "bib" => array(
- "default" => 'Last, First. Title, n.d. ',
- "apa" => 'Last, F. (n.d.). Title. '
- )
- );
-
- $key = API::createItem("book", array(
- "title" => "Title 2",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- ), null, 'key');
- self::$items[$key] = array(
- "citation" => array(
- "default" => 'Last, Title 2. ',
- "apa" => '(Last, n.d.) '
- ),
- "bib" => array(
- "default" => 'Last, First. Title 2. Edited by Ed McEditor, n.d. ',
- "apa" => 'Last, F. (n.d.). Title 2. (E. McEditor, Ed.). '
- )
- );
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testContentCitationSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey']
- . "&content=citation"
- . ($style == "default" ? "" : "&style=$style")
- );
- $this->assert200($response);
- $content = API::getContentFromResponse($response);
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString($expected['citation'][$style], $content);
- }
- }
- }
-
-
- public function testContentCitationMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey']
- . "&itemKey=$keyStr&content=citation"
- . ($style == "default" ? "" : "&style=$style")
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(sizeOf($keys), (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key]['citation'][$style], $content);
- }
- }
- }
-
-
- public function testContentBibSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=bib"
- . ($style == "default" ? "" : "&style=$style")
- );
- $this->assert200($response);
- $content = API::getContentFromResponse($response);
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString($expected['bib'][$style], $content);
- }
- }
- }
-
-
- public function testContentBibMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey']
- . "&itemKey=$keyStr&content=bib"
- . ($style == "default" ? "" : "&style=$style")
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(sizeOf($keys), (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key]['bib'][$style], $content);
- }
- }
- }
-}
diff --git a/tests/remote/tests/API/2/CacheTest.php b/tests/remote/tests/API/2/CacheTest.php
deleted file mode 100644
index d9dab0d7..00000000
--- a/tests/remote/tests/API/2/CacheTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class CacheTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- /**
- * An object type's primary data cache for a library has to be created before
- *
- */
- public function testCacheCreatorPrimaryData() {
- $data = array(
- "title" => "Title",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- );
-
- $key = API::createItem("book", $data, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=csljson"
- );
- $json = json_decode(API::getContentFromResponse($response));
- $this->assertEquals("First", $json->author[0]->given);
- $this->assertEquals("Last", $json->author[0]->family);
- $this->assertEquals("Ed", $json->editor[0]->given);
- $this->assertEquals("McEditor", $json->editor[0]->family);
- }
-}
diff --git a/tests/remote/tests/API/2/CollectionTest.php b/tests/remote/tests/API/2/CollectionTest.php
deleted file mode 100644
index 6440e50a..00000000
--- a/tests/remote/tests/API/2/CollectionTest.php
+++ /dev/null
@@ -1,346 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API, stdClass;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class CollectionTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewCollection() {
- $name = "Test Collection";
-
- $xml = API::createCollection($name, false, $this, 'atom');
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $data = API::parseDataFromAtomEntry($xml);
-
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
-
- return $data;
- }
-
-
- /**
- * @depends testNewCollection
- */
- public function testNewSubcollection($data) {
- $name = "Test Subcollection";
- $parent = $data['key'];
-
- $xml = API::createCollection($name, $parent, $this, 'atom');
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
- $this->assertEquals($parent, (string) $json->parentCollection);
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$parent?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:entry/zapi:numCollections')));
- }
-
-
- public function testNewMultipleCollections() {
- $xml = API::createCollection("Test Collection 1", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
-
- $name1 = "Test Collection 2";
- $name2 = "Test Subcollection";
- $parent2 = $data['key'];
-
- $json = array(
- "collections" => array(
- array(
- 'name' => $name1
- ),
- array(
- 'name' => $name2,
- 'parentCollection' => $parent2
- )
- )
- );
-
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
-
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['success']);
- $xml = API::getCollectionXML($json['success']);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $contents = $xml->xpath('/atom:feed/atom:entry/atom:content');
- $content = json_decode(array_shift($contents));
- $this->assertEquals($name1, $content->name);
- $this->assertFalse($content->parentCollection);
- $content = json_decode(array_shift($contents));
- $this->assertEquals($name2, $content->name);
- $this->assertEquals($parent2, $content->parentCollection);
- }
-
-
- public function testEditMultipleCollections() {
- $xml = API::createCollection("Test 1", false, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $key1 = $data['key'];
- $xml = API::createCollection("Test 2", false, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $key2 = $data['key'];
-
- $newName1 = "Test 1 Modified";
- $newName2 = "Test 2 Modified";
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode(array(
- "collections" => array(
- array(
- 'collectionKey' => $key1,
- 'name' => $newName1
- ),
- array(
- 'collectionKey' => $key2,
- 'name' => $newName2
- )
- )
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: " . $data['version']
- )
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['success']);
- $xml = API::getCollectionXML($json['success']);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $contents = $xml->xpath('/atom:feed/atom:entry/atom:content');
- $content = json_decode(array_shift($contents));
- $this->assertEquals($newName1, $content->name);
- $this->assertFalse($content->parentCollection);
- $content = json_decode(array_shift($contents));
- $this->assertEquals($newName2, $content->name);
- $this->assertFalse($content->parentCollection);
- }
-
-
- public function testCollectionItemChange() {
- $collectionKey1 = API::createCollection('Test', false, $this, 'key');
- $collectionKey2 = API::createCollection('Test', false, $this, 'key');
-
- $xml = API::createItem("book", array(
- 'collections' => array($collectionKey1)
- ), $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey1 = $data['key'];
- $itemVersion1 = $data['version'];
- $json = json_decode($data['content']);
- $this->assertEquals(array($collectionKey1), $json->collections);
-
- $xml = API::createItem("journalArticle", array(
- 'collections' => array($collectionKey2)
- ), $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey2 = $data['key'];
- $itemVersion2 = $data['version'];
- $json = json_decode($data['content']);
- $this->assertEquals(array($collectionKey2), $json->collections);
-
- $xml = API::getCollectionXML($collectionKey1);
- $collectionData1 = API::parseDataFromAtomEntry($xml);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
-
- $xml = API::getCollectionXML($collectionKey2);
- $collectionData2 = API::parseDataFromAtomEntry($xml);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
-
- $libraryVersion = API::getLibraryVersion();
-
- // Add items to collection
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey1?key=" . self::$config['apiKey'],
- json_encode(array(
- "collections" => array($collectionKey1, $collectionKey2)
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion1"
- )
- );
- $this->assert204($response);
-
- // Item version should change
- $xml = API::getItemXML($itemKey1);
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals($libraryVersion + 1, $data['version']);
-
- // Collection timestamp shouldn't change, but numItems should
- $xml = API::getCollectionXML($collectionKey2);
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
- $this->assertEquals($collectionData2['version'], $data['version']);
- $collectionData2 = $data;
-
- $libraryVersion = API::getLibraryVersion();
-
- // Remove collections
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey2?key=" . self::$config['apiKey'],
- json_encode(array(
- "collections" => array()
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion2"
- )
- );
- $this->assert204($response);
-
- // Item version should change
- $xml = API::getItemXML($itemKey2);
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals($libraryVersion + 1, $data['version']);
-
- // Collection timestamp shouldn't change, but numItems should
- $xml = API::getCollectionXML($collectionKey2);
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
- $this->assertEquals($collectionData2['version'], $data['version']);
-
- // Check collections arrays and numItems
- $xml = API::getItemXML($itemKey1);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertCount(2, $json->collections);
- $this->assertContains($collectionKey1, $json->collections);
- $this->assertContains($collectionKey2, $json->collections);
-
- $xml = API::getItemXML($itemKey2);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertCount(0, $json->collections);
-
- $xml = API::getCollectionXML($collectionKey1);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
-
- $xml = API::getCollectionXML($collectionKey2);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('//atom:entry/zapi:numItems')));
- }
-
-
- public function testCollectionChildItemError() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $key = API::createItem("book", array(), $this, 'key');
- $xml = API::createNoteItem("Test Note
", $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $json['collections'] = array($collectionKey);
- $json['relations'] = new stdClass;
-
- $libraryVersion = API::getLibraryVersion();
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert400($response);
- $this->assertEquals("Child items cannot be assigned to collections", $response->getBody());
- }
-
-
- public function testCollectionItems() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $xml = API::createItem("book", array('collections' => array($collectionKey)), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey1 = $data['key'];
- $itemVersion1 = $data['version'];
- $json = json_decode($data['content']);
- $this->assertEquals(array($collectionKey), $json->collections);
-
- $xml = API::createItem("journalArticle", array('collections' => array($collectionKey)), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey2 = $data['key'];
- $itemVersion2 = $data['version'];
- $json = json_decode($data['content']);
- $this->assertEquals(array($collectionKey), $json->collections);
-
- $childItemKey1 = API::createAttachmentItem("linked_url", [], $itemKey1, $this, 'key');
- $childItemKey2 = API::createAttachmentItem("linked_url", [], $itemKey2, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(4, $keys);
- $this->assertContains($itemKey1, $keys);
- $this->assertContains($itemKey2, $keys);
- $this->assertContains($childItemKey1, $keys);
- $this->assertContains($childItemKey2, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($itemKey1, $keys);
- $this->assertContains($itemKey2, $keys);
- }
-}
-?>
diff --git a/tests/remote/tests/API/2/CreatorTest.php b/tests/remote/tests/API/2/CreatorTest.php
deleted file mode 100644
index df467d6c..00000000
--- a/tests/remote/tests/API/2/CreatorTest.php
+++ /dev/null
@@ -1,97 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class CreatorTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testCreatorSummary() {
- $xml = API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => "Test"
- )
- )
- ), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey = $data['key'];
- $json = json_decode($data['content'], true);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test", $creatorSummary);
-
- $json['creators'][] = array(
- "creatorType" => "author",
- "firstName" => "Alice",
- "lastName" => "Foo"
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($itemKey);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test and Foo", $creatorSummary);
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
-
- $json['creators'][] = array(
- "creatorType" => "author",
- "firstName" => "Bob",
- "lastName" => "Bar"
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($itemKey);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test et al.", $creatorSummary);
- }
-}
diff --git a/tests/remote/tests/API/2/FileTest.php b/tests/remote/tests/API/2/FileTest.php
deleted file mode 100644
index 2f5eadf7..00000000
--- a/tests/remote/tests/API/2/FileTest.php
+++ /dev/null
@@ -1,1301 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API, \HTTP, \SimpleXMLElement, \Sync, \Z_Tests, \ZipArchive;
-require_once 'APITests.inc.php';
-require_once 'include/bootstrap.inc.php';
-
-/**
- * @group s3
- */
-class FileTests extends APITests {
- private static $toDelete = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
-
- // Delete work files
- $delete = array("file", "old", "new", "patch");
- foreach ($delete as $file) {
- if (file_exists("work/$file")) {
- unlink("work/$file");
- }
- }
- clearstatcache();
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $s3Client = Z_Tests::$AWS->createS3();
-
- foreach (self::$toDelete as $file) {
- try {
- $s3Client->deleteObject([
- 'Bucket' => self::$config['s3Bucket'],
- 'Key' => $file
- ]);
- }
- catch (\Aws\S3\Exception\S3Exception $e) {
- if ($e->getAwsErrorCode() == 'NoSuchKey') {
- echo "\n$file not found on S3 to delete\n";
- }
- else {
- throw $e;
- }
- }
- }
- }
-
-
- public function testNewEmptyImportedFileAttachmentItem() {
- $xml = API::createAttachmentItem("imported_file", [], false, $this);
- return API::parseDataFromAtomEntry($xml);
- }
-
-
- /**
- * @depends testNewEmptyImportedFileAttachmentItem
- */
- public function testAddFileAuthorizationErrors($data) {
- $fileContents = self::getRandomUnicodeString();
- $hash = md5($fileContents);
- $mtime = time() * 1000;
- $size = strlen($fileContents);
- $filename = "test_" . $fileContents;
-
- $fileParams = array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => "text/plain",
- "charset" => "utf-8"
- );
-
- // Check required params
- foreach (array("md5", "filename", "filesize", "mtime") as $exclude) {
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams($fileParams, array($exclude)),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
- }
-
- // Seconds-based mtime
- $fileParams2 = $fileParams;
- $fileParams2['mtime'] = round($mtime / 1000);
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams($fileParams2),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- // TODO: Enable this test when the dataserver enforces it
- //$this->assert400($response);
- //$this->assertEquals('mtime must be specified in milliseconds', $response->getBody());
-
- $fileParams = $this->implodeParams($fileParams);
-
- // Invalid If-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . md5("invalidETag")
- )
- );
- $this->assert412($response);
-
- // Missing If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- )
- );
- $this->assert428($response);
-
- // Invalid If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: invalidETag"
- )
- );
- $this->assert400($response);
- }
-
-
- public function testAddFileFull() {
- $xml = API::createItem("book", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $parentKey = $data['key'];
-
- $xml = API::createAttachmentItem("imported_file", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $originalVersion = $data['version'];
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- self::$toDelete[] = "$hash";
-
- // Upload to S3
- $response = HTTP::post(
- $json->url,
- $json->prefix . $fileContents . $json->suffix,
- array(
- "Content-Type: " . $json->contentType
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // No If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- )
- );
- $this->assert428($response);
-
- // Invalid upload key
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- "upload=invalidUploadKey",
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $this->assertEquals($hash, $json->md5);
- $this->assertEquals($filename, $json->filename);
- $this->assertEquals($mtime, $json->mtime);
- $this->assertEquals($contentType, $json->contentType);
- $this->assertEquals($charset, $json->charset);
-
- return array(
- "key" => $data['key'],
- "json" => $json,
- "size" => $size
- );
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileFullParams() {
- $xml = API::createAttachmentItem("imported_file", [], false, $this);
- $data = API::parseDataFromAtomEntry($xml);
-
- // Get serverDateModified
- $serverDateModified = array_get_first($xml->xpath('/atom:entry/atom:updated'));
- sleep(1);
-
- $originalVersion = $data['version'];
-
- // Get a sync timestamp from before the file is updated
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset,
- "params" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- self::$toDelete[] = "$hash";
-
- // Generate form-data -- taken from S3::getUploadPostData()
- $boundary = "---------------------------" . md5(uniqid());
- $prefix = "";
- foreach ($json->params as $key => $val) {
- $prefix .= "--$boundary\r\n"
- . "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"
- . $val . "\r\n";
- }
- $prefix .= "--$boundary\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n";
- $suffix = "\r\n--$boundary--";
-
- // Upload to S3
- $response = HTTP::post(
- $json->url,
- $prefix . $fileContents . $suffix,
- array(
- "Content-Type: multipart/form-data; boundary=$boundary"
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $this->assertEquals($hash, $json->md5);
- $this->assertEquals($filename, $json->filename);
- $this->assertEquals($mtime, $json->mtime);
- $this->assertEquals($contentType, $json->contentType);
- $this->assertEquals($charset, $json->charset);
-
- // Make sure serverDateModified has changed
- $this->assertNotEquals($serverDateModified, array_get_first($xml->xpath('/atom:entry/atom:updated')));
-
- // Make sure version has changed
- $this->assertNotEquals($originalVersion, $data['version']);
-
- // Make sure new attachment is passed via sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertGreaterThan(0, $xml->updated[0]->count());
- }
-
-
- /**
- * @depends testAddFileFull
- */
- public function testAddFileExisting($addFileData) {
- $key = $addFileData['key'];
- $json = $addFileData['json'];
- $md5 = $json->md5;
- $size = $addFileData['size'];
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $json->md5,
- "filename" => $json->filename,
- "filesize" => $size,
- "mtime" => $json->mtime,
- "contentType" => $json->contentType,
- "charset" => $json->charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . $json->md5
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get upload authorization for existing file with different filename
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $json->md5,
- "filename" => $json->filename . '等', // Unicode 1.1 character, to test signature generation
- "filesize" => $size,
- "mtime" => $json->mtime,
- "contentType" => $json->contentType,
- "charset" => $json->charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . $json->md5
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- return array(
- "key" => $key,
- "md5" => $md5,
- "filename" => $json->filename . '等'
- );
- }
-
-
- /**
- * @depends testAddFileExisting
- * @group attachments
- */
- public function testGetFile($addFileData) {
- $key = $addFileData['key'];
- $md5 = $addFileData['md5'];
- $filename = $addFileData['filename'];
-
- // Get in view mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file/view?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertRegExp('/^https:\/\/[^\/]+\/[0-9]+\//', $location);
- $filenameEncoded = rawurlencode($filename);
- $this->assertEquals($filenameEncoded, substr($location, -1 * strlen($filenameEncoded)));
-
- // Get from view mode
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($md5, md5($response->getBody()));
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($md5, md5($response->getBody()));
-
- return array(
- "key" => $key,
- "response" => $response
- );
- }
-
-
- /**
- * @depends testGetFile
- * @group classic-sync
- */
- public function testAddFilePartial($getFileData) {
- // Get serverDateModified
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $serverDateModified = (string) array_get_first($xml->xpath('/atom:entry/atom:updated'));
- sleep(1);
-
- $data = API::parseDataFromAtomEntry($xml);
- $originalVersion = $data['version'];
-
- // Get a sync timestamp from before the file is updated
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- $oldFilename = "work/old";
- $fileContents = $getFileData['response']->getBody();
- file_put_contents($oldFilename, $fileContents);
-
- $newFilename = "work/new";
- $patchFilename = "work/patch";
-
- $algorithms = array(
- "bsdiff" => "bsdiff "
- . escapeshellarg($oldFilename) . " "
- . escapeshellarg($newFilename) . " "
- . escapeshellarg($patchFilename),
- "xdelta" => "xdelta3 -f -e -9 -S djw -s "
- . escapeshellarg($oldFilename) . " "
- . escapeshellarg($newFilename) . " "
- . escapeshellarg($patchFilename),
- "vcdiff" => "vcdiff encode "
- . "-dictionary " . escapeshellarg($oldFilename) . " "
- . " -target " . escapeshellarg($newFilename) . " "
- . " -delta " . escapeshellarg($patchFilename)
- );
-
- foreach ($algorithms as $algo => $cmd) {
- clearstatcache();
-
- // Create random contents
- file_put_contents($newFilename, uniqid(self::getRandomUnicodeString(), true));
- $newHash = md5_file($newFilename);
-
- // Get upload authorization
- $fileParams = array(
- "md5" => $newHash,
- "filename" => "test_" . $fileContents,
- "filesize" => filesize($newFilename),
- "mtime" => filemtime($newFilename) * 1000,
- "contentType" => "text/plain",
- "charset" => "utf-8"
- );
- $response = API::userPost(
- self::$config['userID'],
- "items/{$getFileData['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams($fileParams),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . md5_file($oldFilename)
- )
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- exec($cmd, $output, $ret);
- if ($ret != 0) {
- echo "Warning: Error running $algo -- skipping file upload test\n";
- continue;
- }
-
- $patch = file_get_contents($patchFilename);
- $this->assertNotEquals("", $patch);
-
- self::$toDelete[] = "$newHash";
-
- // Upload patch file
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$getFileData['key']}/file?key=" . self::$config['apiKey']
- . "&algorithm=$algo&upload=" . $json->uploadKey,
- $patch,
- array(
- "If-Match: " . md5_file($oldFilename)
- )
- );
- $this->assert204($response);
-
- unlink($patchFilename);
- rename($newFilename, $oldFilename);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($fileParams['md5'], $json->md5);
- $this->assertEquals($fileParams['mtime'], $json->mtime);
- $this->assertEquals($fileParams['contentType'], $json->contentType);
- $this->assertEquals($fileParams['charset'], $json->charset);
-
- // Make sure version has changed
- $this->assertNotEquals($originalVersion, $data['version']);
-
- // Make sure new attachment is passed via sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertGreaterThan(0, $xml->updated[0]->count());
-
- // Verify file on S3
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}/file?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
-
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileParams['md5'], md5($response->getBody()));
- $t = $fileParams['contentType'];
- $this->assertEquals(
- $t . (($t && $fileParams['charset']) ? "; charset={$fileParams['charset']}" : ""),
- $response->getHeader("Content-Type")
- );
- }
- }
-
-
- public function testExistingFileWithOldStyleFilename() {
- $fileContents = self::getRandomUnicodeString();
- $hash = md5($fileContents);
- $filename = 'test.txt';
- $size = strlen($fileContents);
-
- $parentKey = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_file", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- $originalVersion = $data['version'];
- $mtime = time() * 1000;
- $contentType = 'text/plain';
- $charset = 'utf-8';
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- // Upload to old-style location
- self::$toDelete[] = "$hash/$filename";
- self::$toDelete[] = "$hash";
- $s3Client = Z_Tests::$AWS->createS3();
- $s3Client->putObject([
- 'Bucket' => self::$config['s3Bucket'],
- 'Key' => $hash . '/' . $filename,
- 'Body' => $fileContents
- ]);
-
- // Register upload
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey'],
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // The file should be accessible on the item at the old-style location
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})/' . $filename . '\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get upload authorization for the same file and filename on another item, which should
- // result in 'exists', even though we uploaded to the old-style location
- $parentKey = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_file", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})/' . $filename . '\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileContents, $response->getBody());
- $this->assertEquals($contentType . '; charset=' . $charset, $response->getHeader('Content-Type'));
-
- // Get upload authorization for the same file and different filename on another item,
- // which should result in 'exists' and a copy of the file to the hash-only location
- $parentKey = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_file", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- // Also use a different content type
- $contentType = 'application/x-custom';
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => "test2.txt",
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file?key=" . self::$config['apiKey']
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileContents, $response->getBody());
- $this->assertEquals($contentType, $response->getHeader('Content-Type'));
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClient() {
- API::userClear(self::$config['userID']);
-
- $fileContentType = "text/html";
- $fileCharset = "utf-8";
-
- $auth = array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- );
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $xml = API::createAttachmentItem("imported_file", [], false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $originalVersion = $data['version'];
- $json = json_decode($data['content']);
- $json->contentType = $fileContentType;
- $json->charset = $fileCharset;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $originalVersion = $response->getHeader("Last-Modified-Version");
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- // Get file info
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1&info=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $xml = new SimpleXMLElement($response->getBody());
-
- self::$toDelete[] = "$hash";
-
- $boundary = "---------------------------" . rand();
- $postData = "";
- foreach ($xml->params->children() as $key => $val) {
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"$key\"\r\n\r\n$val\r\n";
- }
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"file\"\r\n\r\n" . $fileContents . "\r\n";
- $postData .= "--" . $boundary . "--";
-
- // Upload to S3
- $response = HTTP::post(
- (string) $xml->url,
- $postData,
- array(
- "Content-Type: multipart/form-data; boundary=" . $boundary
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // Invalid upload key
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- "update=invalidUploadKey&mtime=" . $mtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert400($response);
-
- // No mtime
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert500($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key . "&mtime=" . $mtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $this->assertEquals($hash, $json->md5);
- $this->assertEquals($filename, $json->filename);
- $this->assertEquals($mtime, $json->mtime);
-
- // Make sure attachment item wasn't updated (or else the client
- // will get a conflict when it tries to update the metadata)
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- )
- );
- $this->assert200($response);
- $mtime = $response->getBody();
- $this->assertRegExp('/^[0-9]{10}$/', $mtime);
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime + 1000
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // File exists with different filename
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename . '等', // Unicode 1.1 character, to test signature generation
- "filesize" => $size,
- "mtime" => $mtime + 1000
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // Make sure attachment item still wasn't updated
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- // Get attachment
- $xml = Sync::updated($sessionID, 2);
- $this->assertEquals(1, $xml->updated[0]->items->count());
- $itemXML = $xml->updated[0]->items[0]->item[0]->asXML();
- $this->assertEquals($fileContentType, (string) $xml->updated[0]->items[0]->item[0]['mimeType']);
- $this->assertEquals($fileCharset, (string) $xml->updated[0]->items[0]->item[0]['charset']);
- $this->assertEquals($hash, (string) $xml->updated[0]->items[0]->item[0]['storageHash']);
- $this->assertEquals($mtime + 1000, (string) $xml->updated[0]->items[0]->item[0]['storageModTime']);
-
- Sync::logout($sessionID);
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClientZip() {
- API::userClear(self::$config['userID']);
-
- $auth = array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- );
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $xml = API::createItem("book", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
-
- $fileContentType = "text/html";
- $fileCharset = "UTF-8";
- $fileFilename = "file.html";
- $fileModtime = time();
-
- $xml = API::createAttachmentItem("imported_url", [], $key, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- $version = $data['version'];
- $json = json_decode($data['content']);
- $json->contentType = $fileContentType;
- $json->charset = $fileCharset;
- $json->filename = $fileFilename;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- // Get file info
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1&info=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $zip = new ZipArchive();
- $file = "work/$key.zip";
-
- if ($zip->open($file, ZIPARCHIVE::CREATE) !== TRUE) {
- throw new Exception("Cannot open ZIP file");
- }
-
- $zip->addFromString($fileFilename, self::getRandomUnicodeString());
- $zip->addFromString("file.css", self::getRandomUnicodeString());
- $zip->close();
-
- $hash = md5_file($file);
- $filename = $key . ".zip";
- $size = filesize($file);
- $fileContents = file_get_contents($file);
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $fileModtime,
- "zip" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $xml = new SimpleXMLElement($response->getBody());
-
- self::$toDelete[] = "$hash";
-
- $boundary = "---------------------------" . rand();
- $postData = "";
- foreach ($xml->params->children() as $key => $val) {
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"$key\"\r\n\r\n$val\r\n";
- }
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"file\"\r\n\r\n" . $fileContents . "\r\n";
- $postData .= "--" . $boundary . "--";
-
- // Upload to S3
- $response = HTTP::post(
- (string) $xml->url,
- $postData,
- array(
- "Content-Type: multipart/form-data; boundary=" . $boundary
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key . "&mtime=" . $fileModtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $this->assertEquals($hash, $json->md5);
- $this->assertEquals($fileFilename, $json->filename);
- $this->assertEquals($fileModtime, $json->mtime);
-
- // Make sure attachment item wasn't updated (or else the client
- // will get a conflict when it tries to update the metadata)
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- )
- );
- $this->assert200($response);
- $mtime = $response->getBody();
- $this->assertRegExp('/^[0-9]{10}$/', $mtime);
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $fileModtime + 1000,
- "zip" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // Make sure attachment item still wasn't updated
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
- }
-
-
- public function testAddFileLinkedAttachment() {
- $xml = API::createAttachmentItem("linked_file", [], false, $this);
- $data = API::parseDataFromAtomEntry($xml);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$data['key']}/file?key=" . self::$config['apiKey'],
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
- }
-
-
- private function implodeParams($params, $exclude=array()) {
- $parts = array();
- foreach ($params as $key => $val) {
- if (in_array($key, $exclude)) {
- continue;
- }
- $parts[] = $key . "=" . urlencode($val);
- }
- return implode("&", $parts);
- }
-
-
- private function getRandomUnicodeString() {
- return "Âéìøü 这是一个测试。 " . uniqid();
- }
-}
diff --git a/tests/remote/tests/API/2/FullTextTest.php b/tests/remote/tests/API/2/FullTextTest.php
deleted file mode 100644
index e5faf9be..00000000
--- a/tests/remote/tests/API/2/FullTextTest.php
+++ /dev/null
@@ -1,325 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-/**
- * @group fulltext
- */
-class FullTextTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testSetItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey']
- );
- $this->assert404($response);
- $this->assertNull($response->getHeader("Last-Modified-Version"));
-
- $libraryVersion = API::getLibraryVersion();
-
- $content = "Here is some full-text content";
- $pages = 50;
-
- // No Content-Type
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- $content
- );
- $this->assert400($response, "Content-Type must be application/json");
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages,
- "invalidParam" => "shouldBeIgnored"
- ]),
- array("Content-Type: application/json")
- );
-
- $this->assert204($response);
- $contentVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($libraryVersion, $contentVersion);
-
- // Retrieve it
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($content, $json['content']);
- $this->assertArrayHasKey('indexedPages', $json);
- $this->assertArrayHasKey('totalPages', $json);
- $this->assertEquals($pages, $json['indexedPages']);
- $this->assertEquals($pages, $json['totalPages']);
- $this->assertArrayNotHasKey("indexedChars", $json);
- $this->assertArrayNotHasKey("invalidParam", $json);
- $this->assertEquals($contentVersion, $response->getHeader("Last-Modified-Version"));
- }
-
-
- public function testModifyAttachmentWithFulltext() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $content = "Here is some full-text content";
- $pages = 50;
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- $json = json_decode($data['content'], true);
- $json['title'] = "This is a new attachment title";
- $json['contentType'] = 'text/plain';
-
- // Modify attachment item
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $data['version'])
- );
- $this->assert204($response);
- }
-
-
- public function testNewerContent() {
- API::userClear(self::$config['userID']);
-
- // Store content for one item
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $key1 = $data['key'];
-
- $content = "Here is some full-text content";
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key1/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion1 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan(0, $contentVersion1);
-
- // And another
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $key2 = $data['key'];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key2/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan(0, $contentVersion2);
-
- // Get newer one
- $response = API::userGet(
- self::$config['userID'],
- "fulltext?key=" . self::$config['apiKey'] . "&newer=$contentVersion1"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $this->assertEquals($contentVersion2, $response->getHeader("Last-Modified-Version"));
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $this->assertArrayHasKey($key2, $json);
- $this->assertEquals($contentVersion2, $json[$key2]);
-
- // Get both with newer=0
- $response = API::userGet(
- self::$config['userID'],
- "fulltext?key=" . self::$config['apiKey'] . "&newer=0"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json);
- $this->assertArrayHasKey($key1, $json);
- $this->assertEquals($contentVersion1, $json[$key1]);
- $this->assertArrayHasKey($key1, $json);
- $this->assertEquals($contentVersion2, $json[$key2]);
- }
-
-
- public function testSearchItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey']
- );
- $this->assert404($response);
-
- $content = "Here is some unique full-text content";
- $pages = 50;
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages
- ]),
- array("Content-Type: application/json")
- );
-
- $this->assert204($response);
-
- // Wait for refresh
- sleep(1);
-
- // Search for a word
- $response = API::userGet(
- self::$config['userID'],
- "items?q=unique&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($data['key'], trim($response->getBody()));
-
- // Search for a phrase
- $response = API::userGet(
- self::$config['userID'],
- "items?q=unique%20full-text&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($data['key'], trim($response->getBody()));
-
- // Search for nonexistent word
- $response = API::userGet(
- self::$config['userID'],
- "items?q=nothing&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals("", trim($response->getBody()));
- }
-
-
- public function testDeleteItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_file", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
-
- $content = "Ыюм мютат дэбетиз конвынёры эю, ку мэль жкрипта трактатоз.\nПро ут чтэт эрепюят граэкйж, дуо нэ выро рыкючабо пырикюлёз.";
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => $content,
- "indexedPages" => 50
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion = $response->getHeader("Last-Modified-Version");
-
- // Retrieve it
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($content, $json['content']);
- $this->assertEquals(50, $json['indexedPages']);
-
- // Set to empty string
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey'],
- json_encode([
- "content" => ""
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $this->assertGreaterThan($contentVersion, $response->getHeader("Last-Modified-Version"));
-
- // Make sure it's gone
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}/fulltext?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals("", $json['content']);
- $this->assertArrayNotHasKey("indexedPages", $json);
- }
-}
diff --git a/tests/remote/tests/API/2/GeneralTest.php b/tests/remote/tests/API/2/GeneralTest.php
deleted file mode 100644
index c8d7b975..00000000
--- a/tests/remote/tests/API/2/GeneralTest.php
+++ /dev/null
@@ -1,98 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class GeneralTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- public function testZoteroWriteToken() {
- $json = API::getItemTemplate("book");
-
- $token = md5(uniqid());
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array(
- "Content-Type: application/json",
- "Zotero-Write-Token: $token"
- )
- );
- $this->assert200ForObject($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array(
- "Content-Type: application/json",
- "Zotero-Write-Token: $token"
- )
- );
- $this->assert412($response);
- }
-
-
- public function testInvalidCharacters() {
- $data = array(
- 'title' => "A" . chr(0) . "A",
- 'creators' => array(
- array(
- 'creatorType' => "author",
- 'name' => "B" . chr(1) . "B"
- )
- ),
- 'tags' => array(
- array(
- 'tag' => "C" . chr(2) . "C"
- )
- )
- );
- $xml = API::createItem("book", $data, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals("AA", $json->title);
- $this->assertEquals("BB", $json->creators[0]->name);
- $this->assertEquals("CC", $json->tags[0]->tag);
- }
-}
diff --git a/tests/remote/tests/API/2/GroupTest.php b/tests/remote/tests/API/2/GroupTest.php
deleted file mode 100644
index 5e236d88..00000000
--- a/tests/remote/tests/API/2/GroupTest.php
+++ /dev/null
@@ -1,142 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API, \SimpleXMLElement;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class GroupTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
- /**
- * Changing a group's metadata should change its ETag
- */
- public function testUpdateMetadata() {
- $response = API::userGet(
- self::$config['userID'],
- "groups?content=json&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
-
- // Get group API URI and ETag
- $xml = API::getXMLFromResponse($response);
- $xml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
- $xml->registerXPathNamespace('zapi', 'http://zotero.org/ns/api');
- $groupID = (string) array_get_first($xml->xpath("//atom:entry/zapi:groupID"));
- $url = (string) array_get_first($xml->xpath("//atom:entry/atom:link[@rel='self']/@href"));
- $url = str_replace(self::$config['apiURLPrefix'], '', $url);
- $etag = (string) array_get_first($xml->xpath("//atom:entry/atom:content/@etag"));
-
- // Make sure format=etags returns the same ETag
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=etags&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertEquals($etag, $json->$groupID);
-
- // Update group metadata
- $json = json_decode(array_get_first($xml->xpath("//atom:entry/atom:content")));
- $xml = new SimpleXMLElement(" ");
- foreach ($json as $key => $val) {
- switch ($key) {
- case 'id':
- case 'members':
- continue;
-
- case 'name':
- $name = "My Test Group " . uniqid();
- $xml['name'] = $name;
- break;
-
- case 'description':
- $description = "This is a test description " . uniqid();
- $xml->$key = $description;
- break;
-
- case 'url':
- $urlField = "http://example.com/" . uniqid();
- $xml->$key = $urlField;
- break;
-
- default:
- $xml[$key] = $val;
- }
- }
- $xml = trim(preg_replace('/^<\?xml.+\n/', "", $xml->asXML()));
-
- $response = API::put(
- $url,
- $xml,
- array("Content-Type: text/xml"),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $xml->registerXPathNamespace('zxfer', 'http://zotero.org/ns/transfer');
- $group = $xml->xpath('//atom:entry/atom:content/zxfer:group');
- $this->assertCount(1, $group);
- $this->assertEquals($name, $group[0]['name']);
-
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=etags&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $newETag = $json->$groupID;
- $this->assertNotEquals($etag, $newETag);
-
- // Check ETag header on individual group request
- $response = API::groupGet(
- $groupID,
- "?content=json&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($newETag, $response->getHeader('ETag'));
- $json = json_decode(API::getContentFromResponse($response));
- $this->assertEquals($name, $json->name);
- $this->assertEquals($description, $json->description);
- $this->assertEquals($urlField, $json->url);
- }
-}
-?>
diff --git a/tests/remote/tests/API/2/ItemTest.php b/tests/remote/tests/API/2/ItemTest.php
deleted file mode 100644
index 6fc60a0b..00000000
--- a/tests/remote/tests/API/2/ItemTest.php
+++ /dev/null
@@ -1,1376 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class ItemTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function testNewEmptyBookItem() {
- $xml = API::createItem("book", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals("book", (string) $json->itemType);
- return $data;
- }
-
-
- public function testNewEmptyBookItemMultiple() {
- $json = API::getItemTemplate("book");
-
- $data = array();
- $json->title = "A";
- $data[] = $json;
- $json2 = clone $json;
- $json2->title = "B";
- $data[] = $json2;
- $json3 = clone $json;
- $json3->title = "C";
- $data[] = $json3;
-
- $response = API::postItems($data);
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- $xml = API::getItemXML($json['success'], $this);
- $contents = $xml->xpath('/atom:feed/atom:entry/atom:content');
-
- $content = json_decode(array_shift($contents));
- $this->assertEquals("A", $content->title);
- $content = json_decode(array_shift($contents));
- $this->assertEquals("B", $content->title);
- $content = json_decode(array_shift($contents));
- $this->assertEquals("C", $content->title);
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testEditBookItem($newItemData) {
- $key = $newItemData['key'];
- $version = $newItemData['version'];
- $json = json_decode($newItemData['content']);
-
- $newTitle = "New Title";
- $numPages = 100;
- $creatorType = "author";
- $firstName = "Firstname";
- $lastName = "Lastname";
-
- $json->title = $newTitle;
- $json->numPages = $numPages;
- $json->creators[] = array(
- 'creatorType' => $creatorType,
- 'firstName' => $firstName,
- 'lastName' => $lastName
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $this->assertEquals($newTitle, $json->title);
- $this->assertEquals($numPages, $json->numPages);
- $this->assertEquals($creatorType, $json->creators[0]->creatorType);
- $this->assertEquals($firstName, $json->creators[0]->firstName);
- $this->assertEquals($lastName, $json->creators[0]->lastName);
-
- return API::parseDataFromAtomEntry($xml);
- }
-
-
- public function testDateAdded() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test"
- );
- $xml = API::createItem("videoRecording", $itemData, $this, 'atom');
- break;
- }
-
- $newDateAdded = "2013-03-03 21:33:53";
-
- $data = API::parseDataFromAtomEntry($xml);
- $objectKey = $data['key'];
- $json = json_decode($data['content'], true);
-
- $json['title'] = "Test 2";
- $json['dateAdded'] = $newDateAdded;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert400($response, "'dateAdded' cannot be modified for existing $objectTypePlural");
- }
-
-
- public function testDateModified() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test"
- );
- $xml = API::createItem("videoRecording", $itemData, $this, 'atom');
- break;
- }
-
- $data = API::parseDataFromAtomEntry($xml);
- $objectKey = $data['key'];
- $json = json_decode($data['content'], true);
- $dateModified1 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If no explicit dateModified, use current timestamp
- //
- $json['title'] = "Test 2";
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $xml = API::getItemXML($objectKey);
- break;
- }
-
- $dateModified2 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertNotEquals($dateModified1, $dateModified2);
- $json = json_decode(API::parseDataFromAtomEntry($xml)['content'], true);
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If existing dateModified, use current timestamp
- //
- $json['title'] = "Test 3";
- $json['dateModified'] = trim(preg_replace("/[TZ]/", " ", $dateModified2));
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $xml = API::getItemXML($objectKey);
- break;
- }
-
- $dateModified3 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertNotEquals($dateModified2, $dateModified3);
- $json = json_decode(API::parseDataFromAtomEntry($xml)['content'], true);
-
- //
- // If explicit dateModified, use that
- //
- $newDateModified = "2013-03-03 21:33:53";
- $json['title'] = "Test 4";
- $json['dateModified'] = $newDateModified;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $xml = API::getItemXML($objectKey);
- break;
- }
- $dateModified4 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertEquals($newDateModified, trim(preg_replace("/[TZ]/", " ", $dateModified4)));
- }
-
-
- public function testDateAccessedInvalid() {
- $date = 'February 1, 2014';
- $xml = API::createItem("book", array(
- 'accessDate' => $date
- ), $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- // Invalid dates should be ignored
- $this->assertEmpty($json['accessDate']);
- }
-
-
- public function testChangeItemType() {
- $json = API::getItemTemplate("book");
- $json->title = "Foo";
- $json->numPages = 100;
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $key = API::getFirstSuccessKeyFromResponse($response);
- $xml = API::getItemXML($key, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $version = $data['version'];
- $json1 = json_decode($data['content']);
-
- $json2 = API::getItemTemplate("bookSection");
- unset($json2->attachments);
- unset($json2->notes);
-
- foreach ($json2 as $field => &$val) {
- if ($field != "itemType" && isset($json1->$field)) {
- $val = $json1->$field;
- }
- }
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json2),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals("bookSection", $json->itemType);
- $this->assertEquals("Foo", $json->title);
- $this->assertObjectNotHasAttribute("numPages", $json);
- }
-
-
- //
- // PATCH
- //
- public function testModifyItemPartial() {
- $itemData = array(
- "title" => "Test"
- );
- $xml = API::createItem("book", $itemData, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $itemVersion = $json->itemVersion;
-
- $patch = function ($context, $config, $itemKey, $itemVersion, &$itemData, $newData) {
- foreach ($newData as $field => $val) {
- $itemData[$field] = $val;
- }
- $response = API::userPatch(
- $config['userID'],
- "items/$itemKey?key=" . $config['apiKey'],
- json_encode($newData),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion"
- )
- );
- $context->assert204($response);
- $xml = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
-
- foreach ($itemData as $field => $val) {
- $context->assertEquals($val, $json[$field]);
- }
- $headerVersion = $response->getHeader("Last-Modified-Version");
- $context->assertGreaterThan($itemVersion, $headerVersion);
- $context->assertEquals($json['itemVersion'], $headerVersion);
-
- return $headerVersion;
- };
-
- $newData = array(
- "date" => "2013"
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
-
- $newData = array(
- "title" => ""
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
-
- $newData = array(
- "tags" => array(
- array(
- "tag" => "Foo"
- )
- )
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
-
- $newData = array(
- "tags" => array()
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
-
- $key = API::createCollection('Test', false, $this, 'key');
- $newData = array(
- "collections" => array($key)
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
-
- $newData = array(
- "collections" => array()
- );
- $itemVersion = $patch($this, self::$config, $data['key'], $itemVersion, $itemData, $newData);
- }
-
-
- public function testNewComputerProgramItem() {
- $xml = API::createItem("computerProgram", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
- $json = json_decode($data['content']);
- $this->assertEquals("computerProgram", (string) $json->itemType);
-
- $version = "1.0";
- $json->version = $version;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: {$data['version']}"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($version, $json->version);
-
- // 'versionNumber' from v3 should work too
- unset($json->version);
- $version = "1.1";
- $json->versionNumber = $version;
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($version, $json->version);
- }
-
-
- public function testNewInvalidBookItem() {
- $json = API::getItemTemplate("book");
-
- // Missing item type
- $json2 = clone $json;
- unset($json2->itemType);
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json2)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'itemType' property not provided");
-
- // contentType on non-attachment
- $json2 = clone $json;
- $json2->contentType = "text/html";
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json2)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'contentType' is valid only for attachment items");
-
- // more tests
- }
-
-
- public function testEditTopLevelNote() {
- $xml = API::createNoteItem("Test
", null, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $noteText = "Test Test
";
- $json['note'] = $noteText;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals($noteText, $json['note']);
- }
-
-
- public function testEditChildNote() {
- $key = API::createItem("book", [ "title" => "Test" ], $this, 'key');
- $xml = API::createNoteItem("Test
", $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $noteText = "Test Test
";
- $json['note'] = $noteText;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals($noteText, $json['note']);
- }
-
-
- public function testEditTitleWithCollectionInMultipleMode() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $xml = API::createItem("book", [
- "title" => "A",
- "collections" => [
- $collectionKey
- ]
- ], $this, 'atom');
-
- $data = API::parseDataFromAtomEntry($xml);
- $data = json_decode($data['content'], true);
- $version = $data['itemVersion'];
- $data['title'] = "B";
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "items" => [$data]
- ])
- );
- $this->assert200ForObject($response);
-
- $xml = API::getItemXML($data['itemKey']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals("B", $json['title']);
- $this->assertGreaterThan($version, $json['itemVersion']);
- }
-
-
- public function testEditTitleWithTagInMultipleMode() {
- $tag1 = [
- "tag" => "foo",
- "type" => 1
- ];
- $tag2 = [
- "tag" => "bar"
- ];
-
- $xml = API::createItem("book", [
- "title" => "A",
- "tags" => [$tag1]
- ], $this, 'atom');
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(1, $json['tags']);
- $this->assertEquals($tag1, $json['tags'][0]);
-
- $version = $json['itemVersion'];
- $json['title'] = "B";
- $json['tags'][] = $tag2;
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "items" => [$json]
- ])
- );
- $this->assert200ForObject($response);
-
- $xml = API::getItemXML($json['itemKey']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals("B", $json['title']);
- $this->assertGreaterThan($version, $json['itemVersion']);
- $this->assertCount(2, $json['tags']);
- $this->assertContains($tag1, $json['tags']);
- $this->assertContains($tag2, $json['tags']);
- }
-
-
- public function testNewTopLevelImportedFileAttachment() {
- $response = API::get("items/new?itemType=attachment&linkMode=imported_file");
- $json = json_decode($response->getBody());
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- }
-
-
- /*
- Disabled -- see note at Zotero_Item::checkTopLevelAttachment()
-
- public function testNewInvalidTopLevelAttachment() {
- $linkModes = array("linked_url", "imported_url");
- foreach ($linkModes as $linkMode) {
- $response = API::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "Only file attachments and PDFs can be top-level items");
- }
- }
- */
-
-
- public function testNewEmptyLinkAttachmentItem() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("linked_url", [], $key, $this, 'atom');
- return API::parseDataFromAtomEntry($xml);
- }
-
-
- public function testNewEmptyLinkAttachmentItemWithItemKey() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("linked_url", [], $key, $this, 'atom');
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $json->parentItem = $key;
- require_once '../../model/Utilities.inc.php';
- require_once '../../model/ID.inc.php';
- $json->itemKey = \Zotero_ID::getKey();
- $json->itemVersion = 0;
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert200ForObject($response);
- }
-
-
- public function testNewEmptyImportedURLAttachmentItem() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("imported_url", [], $key, $this, 'atom');
- return API::parseDataFromAtomEntry($xml);
- }
-
-
- public function testEditEmptyLinkAttachmentItem() {
- $key = API::createItem("book", false, $this, 'key');
- $xml = API::createAttachmentItem("linked_url", [], $key, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
-
- $key = $data['key'];
- $version = $data['version'];
- $json = json_decode($data['content']);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- // Item shouldn't change
- $this->assertEquals($version, $data['version']);
-
- return $data;
- }
-
-
- /**
- * @depends testNewEmptyImportedURLAttachmentItem
- */
- public function testEditEmptyImportedURLAttachmentItem($newItemData) {
- $key = $newItemData['key'];
- $version = $newItemData['version'];
- $json = json_decode($newItemData['content']);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- // Item shouldn't change
- $this->assertEquals($version, $data['version']);
-
- return $newItemData;
- }
-
-
- /**
- * @depends testEditEmptyLinkAttachmentItem
- */
- public function testEditLinkAttachmentItem($newItemData) {
- $key = $newItemData['key'];
- $version = $newItemData['version'];
- $json = json_decode($newItemData['content']);
-
- $contentType = "text/xml";
- $charset = "utf-8";
-
- $json->contentType = $contentType;
- $json->charset = $charset;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $xml = API::getItemXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($contentType, $json->contentType);
- $this->assertEquals($charset, $json->charset);
- }
-
-
- public function testEditAttachmentUpdatedTimestamp() {
- $xml = API::createAttachmentItem("linked_file", [], false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $atomUpdated = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $json = json_decode($data['content'], true);
- $json['note'] = "Test";
-
- sleep(1);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $data['version'])
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($data['key']);
- $atomUpdated2 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertNotEquals($atomUpdated2, $atomUpdated);
- }
-
-
- public function testNewAttachmentItemInvalidLinkMode() {
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
-
- // Invalid linkMode
- $json->linkMode = "invalidName";
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'invalidName' is not a valid linkMode");
-
- // Missing linkMode
- unset($json->linkMode);
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'linkMode' property not provided");
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testNewAttachmentItemMD5OnLinkedURL($newItemData) {
- $parentKey = $newItemData['key'];
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $json->parentItem = $parentKey;
-
- $json->md5 = "c7487a750a97722ae1878ed46b215ebe";
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'md5' is valid only for imported attachment items");
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testNewAttachmentItemModTimeOnLinkedURL($newItemData) {
- $parentKey = $newItemData['key'];
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $json->parentItem = $parentKey;
-
- $json->mtime = "1332807793000";
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'mtime' is valid only for imported attachment items");
- }
-
-
- public function test_should_not_allow_changing_storage_properties_in_group_libraries() {
- $key = API::groupCreateItem(
- self::$config['ownedPrivateGroupID'], "book", $this, 'key'
- );
- $xml = API::groupCreateAttachmentItem(
- self::$config['ownedPrivateGroupID'], "imported_url", [], $key, $this
- );
- $newItemData = API::parseDataFromAtomEntry($xml);
-
- $key = $newItemData['key'];
- $version = $newItemData['version'];
- $json = json_decode($newItemData['content']);
-
- $props = ["md5", "mtime"];
- foreach ($props as $prop) {
- $json2 = clone $json;
- $json2->$prop = "new" . ucwords($prop);
- $response = API::groupPut(
- self::$config['ownedPrivateGroupID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json2),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert400($response);
- $this->assertEquals("Cannot change '$prop' directly in group library", $response->getBody());
- }
- }
-
-
- public function testMappedCreatorTypes() {
- $json = array(
- "items" => array(
- array(
- 'itemType' => 'presentation',
- 'title' => 'Test',
- 'creators' => array(
- array(
- "creatorType" => "author",
- "name" => "Foo"
- )
- )
- ),
- array(
- 'itemType' => 'presentation',
- 'title' => 'Test',
- 'creators' => array(
- array(
- "creatorType" => "editor",
- "name" => "Foo"
- )
- )
- )
- )
- );
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- // 'author' gets mapped automatically
- $this->assert200ForObject($response);
- // Others don't
- $this->assert400ForObject($response, false, 1);
- }
-
-
- public function testNumChildren() {
- $xml = API::createItem("book", false, $this);
- $this->assertEquals(0, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
-
- API::createAttachmentItem("linked_url", [], $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
-
- API::createNoteItem("Test", $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
- }
-
-
- public function testTop() {
- API::userClear(self::$config['userID']);
-
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $parentTitle1 = "Parent Title";
- $childTitle1 = "This is a Test Title";
- $parentTitle2 = "Another Parent Title";
- $parentTitle3 = "Yet Another Parent Title";
- $noteText = "This is a sample note.";
- $parentTitleSearch = "title";
- $childTitleSearch = "test";
- $dates = ["2013", "January 3, 2010", ""];
- $orderedDates = [$dates[2], $dates[1], $dates[0]];
- $itemTypes = ["journalArticle", "newspaperArticle", "book"];
-
- $parentKeys = [];
- $childKeys = [];
-
- $parentKeys[] = API::createItem($itemTypes[0], [
- 'title' => $parentTitle1,
- 'date' => $dates[0],
- 'collections' => [
- $collectionKey
- ]
- ], $this, 'key');
- $childKeys[] = API::createAttachmentItem("linked_url", [
- 'title' => $childTitle1
- ], $parentKeys[0], $this, 'key');
-
- $parentKeys[] = API::createItem($itemTypes[1], [
- 'title' => $parentTitle2,
- 'date' => $dates[1]
- ], $this, 'key');
- $childKeys[] = API::createNoteItem($noteText, $parentKeys[1], $this, 'key');
-
- // Create item with deleted child that matches child title search
- $parentKeys[] = API::createItem($itemTypes[2], [
- 'title' => $parentTitle3
- ], $this, 'key');
- API::createAttachmentItem("linked_url", [
- 'title' => $childTitle1,
- 'deleted' => true
- ], $parentKeys[sizeOf($parentKeys) - 1], $this, 'key');
-
- // Add deleted item with non-deleted child
- $deletedKey = API::createItem("book", [
- 'title' => "This is a deleted item",
- 'deleted' => true
- ], $this, 'key');
- API::createNoteItem("This is a child note of a deleted item.", $deletedKey, $this, 'key');
-
- // /top, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $xpath);
- }
-
- // /top, Atom, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(sizeOf($parentKeys), $keys);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $keys);
- }
-
- // /top, keys, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for parent, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for parent, Atom, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey']
- . "&content=json&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for parent, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for parent, keys, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey']
- . "&format=keys&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for child, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&itemKey=" . $childKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for child, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys&itemKey=" . $childKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top, Atom, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $xpath);
- }
-
- // /top, Atom, in collection, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey']
- . "&content=json&q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, Atom, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, Atom, in collection, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey']
- . "&content=json&q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- // Not currently possible
- /*$this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);*/
-
- // /top, Atom, with q for all items, ordered by title
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- . "&order=title"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:title');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedTitles = [$parentTitle1, $parentTitle2, $parentTitle3];
- sort($orderedTitles);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedTitles, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by date asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- . "&order=date&sort=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:content');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedResults = array_map(function ($val) {
- return json_decode($val)->date;
- }, $xpath);
- $this->assertEquals($orderedDates, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by date desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- . "&order=date&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:content');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedDatesReverse = array_reverse($orderedDates);
- $orderedResults = array_map(function ($val) {
- return json_decode($val)->date;
- }, $xpath);
- $this->assertEquals($orderedDatesReverse, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- . "&order=itemType"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:itemType');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedItemTypes = $itemTypes;
- sort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedItemTypes, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&content=json&q=$parentTitleSearch"
- . "&order=itemType&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:itemType');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedItemTypes = $itemTypes;
- rsort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedItemTypes, $orderedResults);
- }
-
-
- public function testParentItem() {
- $xml = API::createItem("book", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $parentKey = $data['key'];
- $parentVersion = $data['version'];
-
- $xml = API::createAttachmentItem("linked_url", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $childKey = $data['key'];
- $childVersion = $data['version'];
-
- $this->assertArrayHasKey('parentItem', $json);
- $this->assertEquals($parentKey, $json['parentItem']);
-
- // Remove the parent, making the child a standalone attachment
- unset($json['parentItem']);
-
- // Remove version property, to test header
- unset($json['itemVersion']);
-
- // The parent item version should have been updated when a child
- // was added, so this should fail
- $response = API::userPut(
- self::$config['userID'],
- "items/$childKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $parentVersion)
- );
- $this->assert412($response);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$childKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $childVersion)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($childKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertArrayNotHasKey('parentItem', $json);
- }
-
-
- public function testParentItemPatch() {
- $xml = API::createItem("book", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $parentKey = $data['key'];
- $parentVersion = $data['version'];
-
- $xml = API::createAttachmentItem("linked_url", [], $parentKey, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $childKey = $data['key'];
- $childVersion = $data['version'];
-
- $this->assertArrayHasKey('parentItem', $json);
- $this->assertEquals($parentKey, $json['parentItem']);
-
- $json = array(
- 'title' => 'Test'
- );
-
- // With PATCH, parent shouldn't be removed even though unspecified
- $response = API::userPatch(
- self::$config['userID'],
- "items/$childKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $childVersion)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($childKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertArrayHasKey('parentItem', $json);
- }
-
-
- public function testDate() {
- $date = "Sept 18, 2012";
-
- $xml = API::createItem("book", array(
- "date" => $date
- ), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($date, $json->date);
-
- $this->assertEquals('2012', array_get_first($xml->xpath('/atom:entry/zapi:year')));
- }
-
-
- public function testUnicodeTitle() {
- $title = "Tést";
-
- $xml = API::createItem("book", array("title" => $title), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
-
- // Test entry
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assertContains('"title": "Tést"', $response->getBody());
-
- // Test feed
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assertContains('"title": "Tést"', $response->getBody());
- }
-}
diff --git a/tests/remote/tests/API/2/MappingsTest.php b/tests/remote/tests/API/2/MappingsTest.php
deleted file mode 100644
index 055d8eea..00000000
--- a/tests/remote/tests/API/2/MappingsTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class MappingsTests extends APITests {
- public function testNewItem() {
- $response = API::get("items/new?itemType=invalidItemType");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=book");
- $this->assert200($response);
- $this->assertContentType('application/json', $response);
- $json = json_decode($response->getBody());
- $this->assertEquals('book', $json->itemType);
- }
-
-
- public function testNewItemAttachment() {
- $response = API::get("items/new?itemType=attachment");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=attachment&linkMode=invalidLinkMode");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
- $this->assertObjectHasAttribute('url', $json);
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_file");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
- $this->assertObjectNotHasAttribute('url', $json);
- }
-
-
- public function testComputerProgramVersion() {
- $response = API::get("items/new?itemType=computerProgram");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertObjectHasAttribute('version', $json);
- $this->assertObjectNotHasAttribute('versionNumber', $json);
-
- $response = API::get("itemTypeFields?itemType=computerProgram");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $fields = array_map(function ($val) {
- return $val->field;
- }, $json);
- $this->assertContains('version', $fields);
- $this->assertNotContains('versionNumber', $fields);
- }
-}
-?>
\ No newline at end of file
diff --git a/tests/remote/tests/API/2/NoteTest.php b/tests/remote/tests/API/2/NoteTest.php
deleted file mode 100644
index ec4d6e4f..00000000
--- a/tests/remote/tests/API/2/NoteTest.php
+++ /dev/null
@@ -1,149 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class NoteTests extends APITests {
- private $content;
- private $json;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
- public function setUp() {
- parent::setUp();
-
- // Create too-long note content
- $this->content = str_repeat("1234567890", 25001);
-
- // Create JSON template
- $this->json = API::getItemTemplate("note");
- $this->json->note = $this->content;
- }
-
-
- public function testNoteTooLong() {
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($this->json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123456789...' too long"
- );
- }
-
- // Blank first two lines
- public function testNoteTooLongBlankFirstLines() {
- $this->json->note = " \n \n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($this->json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123456789...' too long"
- );
- }
-
-
- public function testNoteTooLongBlankFirstLinesHTML() {
- $this->json->note = "\n
\n
\n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($this->json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123...' too long"
- );
- }
-
-
- public function testNoteTooLongTitlePlusNewlines() {
- $this->json->note = "Full Text:\n\n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($this->json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note 'Full Text: 1234567890123456789012345678901234567890123456789012345678901234567...' too long"
- );
- }
-
-
- // All content within HTML tags
- public function testNoteTooLongWithinHTMLTags() {
- $this->json->note = " \n";
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($this->json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '<p><!-- 1234567890123456789012345678901234567890123456789012345678901234...' too long"
- );
- }
-}
-?>
diff --git a/tests/remote/tests/API/2/ObjectTest.php b/tests/remote/tests/API/2/ObjectTest.php
deleted file mode 100644
index 55dd2c17..00000000
--- a/tests/remote/tests/API/2/ObjectTest.php
+++ /dev/null
@@ -1,542 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class ObjectTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
- public function tearDown() {
- API::userClear(self::$config['userID']);
- }
-
-
- public function testMultiObjectGet() {
- $this->_testMultiObjectGet('collection');
- $this->_testMultiObjectGet('item');
- $this->_testMultiObjectGet('search');
- }
-
- public function testSingleObjectDelete() {
- $this->_testSingleObjectDelete('collection');
- $this->_testSingleObjectDelete('item');
- $this->_testSingleObjectDelete('search');
- }
-
-
- public function testMultiObjectDelete() {
- $this->_testMultiObjectDelete('collection');
- $this->_testMultiObjectDelete('item');
- $this->_testMultiObjectDelete('search');
- }
-
-
- public function testDeleted() {
- $self = $this;
-
- API::userClear(self::$config['userID']);
-
- //
- // Create objects
- //
- $objectKeys = array();
- $objectKeys['tag'] = array("foo", "bar");
-
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['item'][] = API::createItem(
- "book",
- array(
- "title" => "Title",
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $objectKeys['tag'])
- ),
- $this,
- 'key'
- );
- $objectKeys['item'][] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $objectKeys['item'][] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
-
- // Get library version
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- $libraryVersion1 = $response->getHeader("Last-Modified-Version");
-
- // Delete first object
- $config = self::$config;
- $func = function ($objectType, $libraryVersion) use ($config, $self, $objectKeys) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $response = API::userDelete(
- $config['userID'],
- "$objectTypePlural?key=" . $config['apiKey']
- . "&$keyProp=" . $objectKeys[$objectType][0],
- array("If-Unmodified-Since-Version: " . $libraryVersion)
- );
- $self->assert204($response);
- return $response->getHeader("Last-Modified-Version");
- };
- $tempLibraryVersion = $func('collection', $libraryVersion1);
- $tempLibraryVersion = $func('item', $tempLibraryVersion);
- $tempLibraryVersion = $func('search', $tempLibraryVersion);
- $libraryVersion2 = $tempLibraryVersion;
-
- // Delete second and third objects
- $func = function ($objectType, $libraryVersion) use ($config, $self, $objectKeys) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $response = API::userDelete(
- $config['userID'],
- "$objectTypePlural?key=" . $config['apiKey']
- . "&$keyProp=" . implode(',', array_slice($objectKeys[$objectType], 1)),
- array("If-Unmodified-Since-Version: " . $libraryVersion)
- );
- $self->assert204($response);
- return $response->getHeader("Last-Modified-Version");
- };
- $tempLibraryVersion = $func('collection', $tempLibraryVersion);
- $tempLibraryVersion = $func('item', $tempLibraryVersion);
- $libraryVersion3 = $func('search', $tempLibraryVersion);
-
-
- // Request all deleted objects
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion1"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertNotNull($version);
- $this->assertContentType("application/json", $response);
-
- // Verify keys
- $func = function ($json, $objectType, $objectKeys) use ($self) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $self->assertArrayHasKey($objectTypePlural, $json);
- $self->assertCount(sizeOf($objectKeys), $json[$objectTypePlural]);
- foreach ($objectKeys as $key) {
- $self->assertContains($key, $json[$objectTypePlural]);
- }
- };
- $func($json, 'collection', $objectKeys['collection']);
- $func($json, 'item', $objectKeys['item']);
- $func($json, 'search', $objectKeys['search']);
- // Tags aren't deleted by removing from items
- $func($json, 'tag', []);
-
-
- // Request second and third deleted objects
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion2"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertNotNull($version);
- $this->assertContentType("application/json", $response);
-
- // Verify keys
- $func = function ($json, $objectType, $objectKeys) use ($self) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $self->assertArrayHasKey($objectTypePlural, $json);
- $self->assertCount(sizeOf($objectKeys), $json[$objectTypePlural]);
- foreach ($objectKeys as $key) {
- $self->assertContains($key, $json[$objectTypePlural]);
- }
- };
- $func($json, 'collection', array_slice($objectKeys['collection'], 1));
- $func($json, 'item', array_slice($objectKeys['item'], 1));
- $func($json, 'search', array_slice($objectKeys['search'], 1));
- // Tags aren't deleted by removing from items
- $func($json, 'tag', []);
-
-
- // Explicit tag deletion
- $response = API::userDelete(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&tag=" . implode('%20||%20', $objectKeys['tag']),
- array("If-Unmodified-Since-Version: " . $libraryVersion3)
- );
- $self->assert204($response);
-
- // Verify deleted tags
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion3"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $func($json, 'tag', $objectKeys['tag']);
- }
-
-
- public function testPartialWriteFailure() {
- $this->_testPartialWriteFailure('collection');
- $this->_testPartialWriteFailure('item');
- $this->_testPartialWriteFailure('search');
- }
-
-
- public function testPartialWriteFailureWithUnchanged() {
- $this->_testPartialWriteFailureWithUnchanged('collection');
- $this->_testPartialWriteFailureWithUnchanged('item');
- $this->_testPartialWriteFailureWithUnchanged('search');
- }
-
-
- public function testMultiObjectWriteInvalidObject() {
- $this->_testMultiObjectWriteInvalidObject('collection');
- $this->_testMultiObjectWriteInvalidObject('item');
- $this->_testMultiObjectWriteInvalidObject('search');
- }
-
-
- private function _testMultiObjectGet($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
-
- $keys = [];
- switch ($objectType) {
- case 'collection':
- $keys[] = API::createCollection("Name", false, $this, 'key');
- $keys[] = API::createCollection("Name", false, $this, 'key');
- API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $keys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $keys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $keys[] = API::createSearch("Name", 'default', $this, 'key');
- $keys[] = API::createSearch("Name", 'default', $this, 'key');
- API::createSearch("Name", 'default', $this, 'key');
- break;
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&$keyProp=" . implode(',', $keys)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keys), $response);
-
- // Trailing comma in itemKey parameter
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&$keyProp=" . implode(',', $keys) . ","
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keys), $response);
- }
-
-
- private function _testSingleObjectDelete($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $xml = API::createCollection("Name", false, $this);
- break;
-
- case 'item':
- $xml = API::createItem("book", array("title" => "Title"), $this);
- break;
-
- case 'search':
- $xml = API::createSearch("Name", 'default', $this);
- break;
- }
-
- $data = API::parseDataFromAtomEntry($xml);
- $objectKey = $data['key'];
- $objectVersion = $data['version'];
-
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey']
- );
- $this->assert404($response);
- }
-
-
- private function _testMultiObjectDelete($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
-
- $deleteKeys = array();
- $keepKeys = array();
- switch ($objectType) {
- case 'collection':
- $deleteKeys[] = API::createCollection("Name", false, $this, 'key');
- $deleteKeys[] = API::createCollection("Name", false, $this, 'key');
- $keepKeys[] = API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $deleteKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $deleteKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $keepKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $deleteKeys[] = API::createSearch("Name", 'default', $this, 'key');
- $deleteKeys[] = API::createSearch("Name", 'default', $this, 'key');
- $keepKeys[] = API::createSearch("Name", 'default', $this, 'key');
- break;
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($deleteKeys) + sizeOf($keepKeys), $response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&$keyProp=" . implode(',', $deleteKeys),
- array(
- "If-Unmodified-Since-Version: " . $libraryVersion
- )
- );
- $this->assert204($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keepKeys), $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&$keyProp=" . implode(',', $keepKeys)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keepKeys), $response);
-
- // Add trailing comma to itemKey param, to test key parsing
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&$keyProp=" . implode(',', $keepKeys) . ",",
- array(
- "If-Unmodified-Since-Version: " . $libraryVersion
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- private function _testPartialWriteFailure($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json1 = array("name" => "Test");
- $json2 = array("name" => str_repeat("1234567890", 6554));
- $json3 = array("name" => "Test");
- break;
-
- case 'item':
- $json1 = API::getItemTemplate('book');
- $json2 = clone $json1;
- $json3 = clone $json1;
- $json2->title = str_repeat("1234567890", 6554);
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $json1 = array("name" => "Test", "conditions" => $conditions);
- $json2 = array("name" => str_repeat("1234567890", 6554), "conditions" => $conditions);
- $json3 = array("name" => "Test", "conditions" => $conditions);
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- "$objectTypePlural" => array($json1, $json2, $json3)
- )),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert413ForObject($response, false, 1);
- $this->assert200ForObject($response, false, 2);
- $json = API::getJSONFromResponse($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- foreach ($json['success'] as $key) {
- $this->assertContains($key, $keys);
- }
- }
-
-
- private function _testPartialWriteFailureWithUnchanged($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $data = API::createCollection("Test", false, $this, 'data');
- $json1 = json_decode($data['content']);
- $json2 = array("name" => str_repeat("1234567890", 6554));
- $json3 = array("name" => "Test");
- break;
-
- case 'item':
- $data = API::createItem("book", array("title" => "Title"), $this, 'data');
- $json1 = json_decode($data['content']);
- $json2 = API::getItemTemplate('book');
- $json3 = clone $json2;
- $json2->title = str_repeat("1234567890", 6554);
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $data = API::createSearch("Name", $conditions, $this, 'data');
- $json1 = json_decode($data['content']);
- $json2 = array("name" => str_repeat("1234567890", 6554), "conditions" => $conditions);
- $json3 = array("name" => "Test", "conditions" => $conditions);
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- "$objectTypePlural" => array($json1, $json2, $json3)
- )),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertUnchangedForObject($response, false, 0);
- $this->assert413ForObject($response, false, 1);
- $this->assert200ForObject($response, false, 2);
- $json = API::getJSONFromResponse($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- foreach ($json['success'] as $key) {
- $this->assertContains($key, $keys);
- }
- }
-
-
- private function _testMultiObjectWriteInvalidObject($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode([[]]),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Uploaded data must be a JSON object");
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- "$objectTypePlural" => array(
- "foo" => "bar"
- )
- )),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "'$objectTypePlural' must be an array");
- }
-}
diff --git a/tests/remote/tests/API/2/ParamsTest.php b/tests/remote/tests/API/2/ParamsTest.php
deleted file mode 100644
index a15ebe24..00000000
--- a/tests/remote/tests/API/2/ParamsTest.php
+++ /dev/null
@@ -1,345 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class ParamsTests extends APITests {
- private static $collectionKeys = array();
- private static $itemKeys = array();
- private static $searchKeys = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testFormatKeys() {
- //
- // Collections
- //
- for ($i=0; $i<5; $i++) {
- self::$collectionKeys[] = API::createCollection("Test", false, null, 'key');
- }
-
- //
- // Items
- //
- for ($i=0; $i<5; $i++) {
- self::$itemKeys[] = API::createItem("book", false, null, 'key');
- }
- self::$itemKeys[] = API::createAttachmentItem("imported_file", [], false, null, 'key');
-
- //
- // Searches
- //
- for ($i=0; $i<5; $i++) {
- self::$searchKeys[] = API::createSearch("Test", 'default', null, 'key');
- }
-
- $this->_testFormatKeys('collection');
- $this->_testFormatKeys('item');
- $this->_testFormatKeys('search');
-
- $this->_testFormatKeysSorted('collection');
- $this->_testFormatKeysSorted('item');
- $this->_testFormatKeysSorted('search');
- }
-
-
- private function _testFormatKeys($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keysVar = $objectType . "Keys";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&format=keys"
- );
- $this->assert200($response);
-
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff(self::$$keysVar, $keys), array_diff($keys, self::$$keysVar)
- )
- );
- }
-
-
- private function _testFormatKeysSorted($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keysVar = $objectType . "Keys";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&format=keys&order=title"
- );
- $this->assert200($response);
-
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff(self::$$keysVar, $keys), array_diff($keys, self::$$keysVar)
- )
- );
- }
-
-
- public function testObjectKeyParameter() {
- $this->_testObjectKeyParameter('collection');
- $this->_testObjectKeyParameter('item');
- $this->_testObjectKeyParameter('search');
- }
-
-
- private function _testObjectKeyParameter($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $xmlArray = array();
-
- switch ($objectType) {
- case 'collection':
- $xmlArray[] = API::createCollection("Name", false, $this);
- $xmlArray[] = API::createCollection("Name", false, $this);
- break;
-
- case 'item':
- $xmlArray[] = API::createItem("book", false, $this);
- $xmlArray[] = API::createItem("book", false, $this);
- break;
-
- case 'search':
- $xmlArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this
- );
- $xmlArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this
- );
- break;
- }
-
- $keys = array();
- foreach ($xmlArray as $xml) {
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&content=json&{$objectType}Key={$keys[0]}"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals($keys[0], $data['key']);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&content=json&{$objectType}Key={$keys[0]},{$keys[1]}&order={$objectType}KeyList"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[0], $key);
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[1], $key);
- }
-
-
- public function testCollectionQuickSearch() {
- $title1 = "Test Title";
- $title2 = "Another Title";
-
- $keys = [];
- $keys[] = API::createCollection($title1, [], $this, 'key');
- $keys[] = API::createCollection($title2, [], $this, 'key');
-
- // Search by title
- $response = API::userGet(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'] . "&content=json&q=another"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[1], $key);
-
- // No results
- $response = API::userGet(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'] . "&content=json&q=nothing"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- public function testItemQuickSearch() {
- $title1 = "Test Title";
- $title2 = "Another Title";
- $year2 = "2013";
-
- $keys = [];
- $keys[] = API::createItem("book", [
- 'title' => $title1
- ], $this, 'key');
- $keys[] = API::createItem("journalArticle", [
- 'title' => $title2,
- 'date' => "November 25, $year2"
- ], $this, 'key');
-
- // Search by title
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=" . urlencode($title1)
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[0], $key);
-
- // TODO: Search by creator
-
- // Search by year
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=$year2"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[1], $key);
-
- // Search by year + 1
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=" . ($year2 + 1)
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- public function testItemQuickSearchOrderByDate() {
- $title1 = "Test Title";
- $title2 = "Another Title";
-
- $keys = [];
- $keys[] = API::createItem("book", [
- 'title' => $title1,
- 'date' => "February 12, 2013"
- ], $this, 'key');
- $keys[] = API::createItem("journalArticle", [
- 'title' => $title2,
- 'date' => "November 25, 2012"
- ], $this, 'key');
-
- // Search for one by title
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=" . urlencode($title1)
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[0], $key);
-
- // Search by both by title, date asc
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=title&order=date&sort=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[1], $key);
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[0], $key);
-
- // Search by both by title, date desc
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&content=json&q=title&order=date&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[0], $key);
- $key = (string) array_shift($xpath);
- $this->assertEquals($keys[1], $key);
- }
-}
diff --git a/tests/remote/tests/API/2/PermissionsTest.php b/tests/remote/tests/API/2/PermissionsTest.php
deleted file mode 100644
index 8236a389..00000000
--- a/tests/remote/tests/API/2/PermissionsTest.php
+++ /dev/null
@@ -1,306 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class PermissionsTest extends APITests {
- public function tearDown() {
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryWrite', 1
- );
- }
-
-
- public function testUserGroupsAnonymous() {
- $response = API::get("users/" . self::$config['userID'] . "/groups?content=json");
- $this->assert200($response);
-
- $this->assertTotalResults(self::$config['numPublicGroups'], $response);
-
- // Make sure they're the right groups
- $xml = API::getXMLFromResponse($response);
- $groupIDs = array_map(function ($id) {
- return (int) $id;
- }, $xml->xpath('//atom:entry/zapi:groupID'));
- $this->assertContains(self::$config['ownedPublicGroupID'], $groupIDs);
- $this->assertContains(self::$config['ownedPublicNoAnonymousGroupID'], $groupIDs);
- }
-
-
- public function testUserGroupsOwned() {
- $response = API::get(
- "users/" . self::$config['userID'] . "/groups?content=json"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
-
- $this->assertNumResults(self::$config['numOwnedGroups'], $response);
- $this->assertTotalResults(self::$config['numOwnedGroups'], $response);
- }
-
-
- /**
- * A key without note access shouldn't be able to create a note
- */
- /*public function testKeyNoteAccessWriteError() {
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 0
- );
-
- $response = API::get("items/new?itemType=note");
- $json = json_decode($response->getBody());
- $json->note = "Test";
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert403($response);
- }*/
-
-
- public function testKeyNoteAccess() {
- API::userClear(self::$config['userID']);
-
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 1
- );
-
- $keys = array();
- $topLevelKeys = array();
- $bookKeys = array();
-
- $xml = API::createItem('book', array("title" => "A"), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
- $bookKeys[] = $data['key'];
-
- $xml = API::createNoteItem("B
", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
-
- $xml = API::createNoteItem("C
", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
-
- $xml = API::createNoteItem("D
", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
-
- $xml = API::createNoteItem("E
", false, $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
-
- $xml = API::createItem('book', array("title" => "F"), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
- $topKeys[] = $data['key'];
- $bookKeys[] = $data['key'];
-
- $xml = API::createNoteItem("G
", $data['key'], $this);
- $data = API::parseDataFromAtomEntry($xml);
- $keys[] = $data['key'];
-
- // Create collection and add items to it
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode(array(
- "collections" => array(
- array(
- "name" => "Test",
- "parentCollection" => false
- )
- )
- )),
- array("Content-Type: application/json")
- );
- $this->assert200ForObject($response);
- $collectionKey = API::getFirstSuccessKeyFromResponse($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "collections/$collectionKey/items?key=" . self::$config['apiKey'],
- implode(" ", $topKeys)
- );
- $this->assert204($response);
-
- //
- // format=atom
- //
- // Root
- $response = API::userGet(
- self::$config['userID'], "items?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($keys), $response);
- $this->assertTotalResults(sizeOf($keys), $response);
-
- // Top
- $response = API::userGet(
- self::$config['userID'], "items/top?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($topKeys), $response);
- $this->assertTotalResults(sizeOf($topKeys), $response);
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($topKeys), $response);
- $this->assertTotalResults(sizeOf($topKeys), $response);
-
- //
- // format=keys
- //
- // Root
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($keys), explode("\n", trim($response->getBody())));
-
- // Top
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody())));
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody())));
-
- // Remove notes privilege from key
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryNotes', 0
- );
-
- //
- // format=atom
- //
- // totalResults with limit
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $this->assertNumResults(1, $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // And without limit
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Top
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items?key=" . self::$config['apiKey']
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- //
- // format=keys
- //
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys"
- );
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff($bookKeys, $keys), array_diff($keys, $bookKeys)
- )
- );
- }
-
-
- public function testTagDeletePermissions() {
- API::userClear(self::$config['userID']);
-
- $xml = API::createItem('book', array(
- "tags" => array(
- array(
- "tag" => "A"
- )
- )
- ), $this);
-
- $libraryVersion = API::getLibraryVersion();
-
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryWrite', 0
- );
-
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=A&key=" . self::$config['apiKey']
- );
- $this->assert403($response);
-
- API::setKeyOption(
- self::$config['userID'], self::$config['apiKey'], 'libraryWrite', 1
- );
-
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=A&key=" . self::$config['apiKey'],
- array("If-Unmodified-Since-Version: $libraryVersion")
- );
- $this->assert204($response);
- }
-}
diff --git a/tests/remote/tests/API/2/RelationTest.php b/tests/remote/tests/API/2/RelationTest.php
deleted file mode 100644
index 9bcda45b..00000000
--- a/tests/remote/tests/API/2/RelationTest.php
+++ /dev/null
@@ -1,321 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class RelationTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewItemRelations() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/items/AAAAAAAA",
- "dc:relation" => array(
- "http://zotero.org/users/" . self::$config['userID'] . "/items/AAAAAAAA",
- "http://zotero.org/users/" . self::$config['userID'] . "/items/BBBBBBBB",
- )
- );
-
- $xml = API::createItem("book", array(
- "relations" => $relations
- ), $this);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- if (is_string($object)) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
- else {
- foreach ($object as $rel) {
- $this->assertContains($rel, $json['relations'][$predicate]);
- }
- }
- }
- }
-
-
- public function testRelatedItemRelations() {
- $relations = [
- "owl:sameAs" => "http://zotero.org/groups/1/items/AAAAAAAA",
- ];
-
- $item1JSON = API::createItem("book", [
- "relations" => $relations
- ], $this, 'json');
- $item2JSON = API::createItem("book", null, $this, 'json');
-
- $uriPrefix = "http://zotero.org/users/" . self::$config['userID'] . "/items/";
- $item1URI = $uriPrefix . $item1JSON['itemKey'];
- $item2URI = $uriPrefix . $item2JSON['itemKey'];
-
- // Add item 2 as related item of item 1
- $relations["dc:relation"] = $item2URI;
- $item1JSON["relations"] = $relations;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$item1JSON['itemKey']}?key=" . self::$config['apiKey'],
- json_encode($item1JSON)
- );
- $this->assert204($response);
-
- // Make sure it exists on item 1
- $xml = API::getItemXML($item1JSON['itemKey']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
-
- // And item 2, since related items are bidirectional
- $xml = API::getItemXML($item2JSON['itemKey']);
- $data = API::parseDataFromAtomEntry($xml);
- $item2JSON = json_decode($data['content'], true);
- $this->assertCount(1, $item2JSON['relations']);
- $this->assertEquals($item1URI, $item2JSON["relations"]["dc:relation"]);
-
- // Sending item 2's unmodified JSON back up shouldn't cause the item to be updated.
- // Even though we're sending a relation that's technically not part of the item,
- // when it loads the item it will load the reverse relations too and therefore not
- // add a relation that it thinks already exists.
- $response = API::userPut(
- self::$config['userID'],
- "items/{$item2JSON['itemKey']}?key=" . self::$config['apiKey'],
- json_encode($item2JSON)
- );
- $this->assert204($response);
- $this->assertEquals($item2JSON['itemVersion'], $response->getHeader("Last-Modified-Version"));
- }
-
-
- // Same as above, but in a single request
- public function testRelatedItemRelationsSingleRequest() {
- $uriPrefix = "http://zotero.org/users/" . self::$config['userID'] . "/items/";
- // TEMP: Use autoloader
- require_once '../../model/Utilities.inc.php';
- require_once '../../model/ID.inc.php';
- $item1Key = \Zotero_ID::getKey();
- $item2Key = \Zotero_ID::getKey();
- $item1URI = $uriPrefix . $item1Key;
- $item2URI = $uriPrefix . $item2Key;
-
- $item1JSON = API::getItemTemplate('book');
- $item1JSON->itemKey = $item1Key;
- $item1JSON->itemVersion = 0;
- $item1JSON->relations->{'dc:relation'} = $item2URI;
- $item2JSON = API::getItemTemplate('book');
- $item2JSON->itemKey = $item2Key;
- $item2JSON->itemVersion = 0;
-
- $response = API::postItems([$item1JSON, $item2JSON]);
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- // Make sure it exists on item 1
- $xml = API::getItemXML($item1JSON->itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(1, $json['relations']);
- $this->assertEquals($item2URI, $json['relations']['dc:relation']);
-
- // And item 2, since related items are bidirectional
- $xml = API::getItemXML($item2JSON->itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(1, $json['relations']);
- $this->assertEquals($item1URI, $json['relations']['dc:relation']);
- }
-
-
- public function testInvalidItemRelation() {
- $response = API::createItem("book", array(
- "relations" => array(
- "foo:unknown" => "http://zotero.org/groups/1/items/AAAAAAAA"
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "Unsupported predicate 'foo:unknown'");
-
- $response = API::createItem("book", array(
- "relations" => array(
- "owl:sameAs" => "Not a URI"
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "'relations' values currently must be Zotero item URIs");
-
- $response = API::createItem("book", array(
- "relations" => array(
- "owl:sameAs" => ["Not a URI"]
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "'relations' values currently must be Zotero item URIs");
- }
-
-
- public function testDeleteItemRelation() {
- $relations = array(
- "owl:sameAs" => [
- "http://zotero.org/groups/1/items/AAAAAAAA",
- "http://zotero.org/groups/1/items/BBBBBBBB"
- ],
- "dc:relation" => "http://zotero.org/users/"
- . self::$config['userID'] . "/items/AAAAAAAA",
- );
-
- $data = API::createItem("book", array(
- "relations" => $relations
- ), $this, 'data');
-
- $json = json_decode($data['content'], true);
-
- // Remove a relation
- $json['relations']['owl:sameAs'] = $relations['owl:sameAs'] = $relations['owl:sameAs'][0];
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- // Make sure it's gone
- $xml = API::getItemXML($data['key']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
-
- // Delete all
- $json['relations'] = new \stdClass;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- // Make sure they're gone
- $xml = API::getItemXML($data['key']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(0, $json['relations']);
- }
-
-
- //
- // Collections
- //
- public function testNewCollectionRelations() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- );
-
- $data = API::createCollection("Test", array(
- "relations" => $relations
- ), $this, 'data');
- $json = json_decode($data['content'], true);
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
- }
-
-
- public function testInvalidCollectionRelation() {
- $json = array(
- "name" => "Test",
- "relations" => array(
- "foo:unknown" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- )
- );
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode(array("collections" => array($json)))
- );
- $this->assert400ForObject($response, "Unsupported predicate 'foo:unknown'");
-
- $json["relations"] = array(
- "owl:sameAs" => "Not a URI"
- );
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode(array("collections" => array($json)))
- );
- $this->assert400ForObject($response, "'relations' values currently must be Zotero collection URIs");
-
- $json["relations"] = ["http://zotero.org/groups/1/collections/AAAAAAAA"];
- $response = API::userPost(
- self::$config['userID'],
- "collections?key=" . self::$config['apiKey'],
- json_encode(array("collections" => array($json)))
- );
- $this->assert400ForObject($response, "'relations' property must be an object");
- }
-
-
- public function testDeleteCollectionRelation() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- );
- $data = API::createCollection("Test", array(
- "relations" => $relations
- ), $this, 'data');
- $json = json_decode($data['content'], true);
-
- // Remove all relations
- $json['relations'] = new \stdClass;
- unset($relations['owl:sameAs']);
- $response = API::userPut(
- self::$config['userID'],
- "collections/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
-
- // Make sure it's gone
- $xml = API::getCollectionXML($data['key']);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
- }
-}
diff --git a/tests/remote/tests/API/2/SearchTest.php b/tests/remote/tests/API/2/SearchTest.php
deleted file mode 100644
index c41908a9..00000000
--- a/tests/remote/tests/API/2/SearchTest.php
+++ /dev/null
@@ -1,199 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class SearchTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewSearch() {
- $name = "Test Search";
- $conditions = array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- ),
- array(
- "condition" => "noChildren",
- "operator" => "false",
- "value" => ""
- ),
- array(
- "condition" => "fulltextContent/regexp",
- "operator" => "contains",
- "value" => "/test/"
- )
- );
-
- $xml = API::createSearch($name, $conditions, $this);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:feed/zapi:totalResults')));
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
- $this->assertInternalType('array', $json->conditions);
- $this->assertCount(sizeOf($conditions), $json->conditions);
- foreach ($conditions as $i => $condition) {
- foreach ($condition as $key => $val) {
- $this->assertEquals($val, $json->conditions[$i]->$key);
- }
- }
-
- return $data;
- }
-
-
- /**
- * @depends testNewSearch
- */
- public function testModifySearch($newSearchData) {
- $key = $newSearchData['key'];
- $version = $newSearchData['version'];
- $json = json_decode($newSearchData['content'], true);
-
- // Remove one search condition
- array_shift($json['conditions']);
-
- $name = $json['name'];
- $conditions = $json['conditions'];
-
- $response = API::userPut(
- self::$config['userID'],
- "searches/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
-
- $xml = API::getSearchXML($key);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($name, (string) $json->name);
- $this->assertInternalType('array', $json->conditions);
- $this->assertCount(sizeOf($conditions), $json->conditions);
- foreach ($conditions as $i => $condition) {
- foreach ($condition as $key => $val) {
- $this->assertEquals($val, $json->conditions[$i]->$key);
- }
- }
- }
-
-
- public function testNewSearchNoName() {
- $json = API::createSearch(
- "",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responsejson'
- );
- $this->assert400ForObject($json, "Search name cannot be empty");
- }
-
-
- public function testNewSearchNoConditions() {
- $json = API::createSearch("Test", array(), $this, 'responsejson');
- $this->assert400ForObject($json, "'conditions' cannot be empty");
- }
-
-
- public function testNewSearchConditionErrors() {
- $json = API::createSearch(
- "Test",
- array(
- array(
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responsejson'
- );
- $this->assert400ForObject($json, "'condition' property not provided for search condition");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responsejson'
- );
- $this->assert400ForObject($json, "Search condition cannot be empty");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "title",
- "value" => "test"
- )
- ),
- $this,
- 'responsejson'
- );
- $this->assert400ForObject($json, "'operator' property not provided for search condition");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "title",
- "operator" => "",
- "value" => "test"
- )
- ),
- $this,
- 'responsejson'
- );
- $this->assert400ForObject($json, "Search operator cannot be empty");
- }
-}
diff --git a/tests/remote/tests/API/2/SettingsTest.php b/tests/remote/tests/API/2/SettingsTest.php
deleted file mode 100644
index b1b56b28..00000000
--- a/tests/remote/tests/API/2/SettingsTest.php
+++ /dev/null
@@ -1,458 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class SettingsTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function tearDown() {
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function testAddUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = array(
- "value" => $value
- );
-
- // No version
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert428($response);
-
- // Version must be 0 for non-existent setting
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 1"
- )
- );
- $this->assert412($response);
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- )
- );
- $this->assert204($response);
-
- // Multi-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']);
-
- // Single-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
- }
-
-
- public function testAddUserSettingMultiple() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- // TODO: multiple, once more settings are supported
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = array(
- $settingKey => array(
- "value" => $value
- )
- );
- $response = API::userPost(
- self::$config['userID'],
- "settings?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Multi-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']);
-
- // Single-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
- }
-
-
- public function testAddGroupSettingMultiple() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- // TODO: multiple, once more settings are supported
-
- $groupID = self::$config['ownedPrivateGroupID'];
- $libraryVersion = API::getGroupLibraryVersion($groupID);
-
- $json = array(
- $settingKey => array(
- "value" => $value
- )
- );
- $response = API::groupPost(
- $groupID,
- "settings?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Multi-object GET
- $response = API::groupGet(
- $groupID,
- "settings?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']);
-
- // Single-object GET
- $response = API::groupGet(
- $groupID,
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
- }
-
-
- public function testUpdateUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- // Update with no change
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- $newValue = array(
- array(
- "name" => "_READ",
- "color" => "#CC9933"
- )
- );
- $json['value'] = $newValue;
-
- // Update, no change
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($newValue, $json['value']);
- $this->assertEquals($libraryVersion + 2, $json['version']);
- }
-
-
- public function testDeleteUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Delete
- $response = API::userDelete(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: " . ($libraryVersion + 1)
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert404($response);
-
- $this->assertEquals($libraryVersion + 2, API::getLibraryVersion());
- }
-
-
- public function testDeleteNonexistentSetting() {
- $response = API::userDelete(
- self::$config['userID'],
- "settings/nonexistentSetting?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: 0"
- )
- );
- $this->assert404($response);
- }
-
-
- public function testUnsupportedSetting() {
- $settingKey = "unsupportedSetting";
- $value = true;
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Invalid setting '$settingKey'");
- }
-
-
- public function testUnsupportedSettingMultiple() {
- $settingKey = "unsupportedSetting";
- $json = array(
- "tagColors" => array(
- "value" => array(
- "name" => "_READ",
- "color" => "#990000"
- ),
- "version" => 0
- ),
- $settingKey => array(
- "value" => false,
- "version" => 0
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Invalid setting '$settingKey'");
-
- // Valid setting shouldn't exist, and library version should be unchanged
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey']
- );
- $this->assert404($response);
- $this->assertEquals($libraryVersion, API::getLibraryVersion());
- }
-
-
- public function testOverlongSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => $this->content = str_repeat("abcdefghij", 2001),
- "color" => "#990000"
- )
- );
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "'value' cannot be longer than 20000 characters");
- }
-}
diff --git a/tests/remote/tests/API/2/SortTest.php b/tests/remote/tests/API/2/SortTest.php
deleted file mode 100644
index d7acb67e..00000000
--- a/tests/remote/tests/API/2/SortTest.php
+++ /dev/null
@@ -1,151 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class SortTests extends APITests {
- private static $collectionKeys = [];
- private static $itemKeys = [];
- private static $childAttachmentKeys = [];
- private static $childNoteKeys = [];
- private static $searchKeys = [];
-
- private static $titles = ['q', 'c', 'a', 'j', 'e', 'h', 'i'];
- private static $names = ['m', 's', 'a', 'bb', 'ba', '', ''];
- private static $attachmentTitles = ['v', 'x', null, 'a', null];
- private static $notes = [null, 'aaa', null, null, 'taf'];
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- //
- // Collections
- //
- /*for ($i=0; $i<5; $i++) {
- self::$collectionKeys[] = API::createCollection("Test", false, null, 'key');
- }*/
-
- //
- // Items
- //
- $titles = self::$titles;
- $names = self::$names;
- for ($i = 0; $i < sizeOf(self::$titles) - 2; $i++) {
- $key = API::createItem("book", [
- "title" => array_shift($titles),
- "creators" => [
- [
- "creatorType" => "author",
- "name" => array_shift($names)
- ]
- ]
- ], null, 'key');
-
- // Child attachments
- if (!is_null(self::$attachmentTitles[$i])) {
- self::$childAttachmentKeys[] = API::createAttachmentItem("imported_file", [
- "title" => self::$attachmentTitles[$i]
- ], $key, null, 'key');
- }
- // Child notes
- if (!is_null(self::$notes[$i])) {
- self::$childNoteKeys[] = API::createNoteItem(self::$notes[$i], $key, null, 'key');
- }
-
- self::$itemKeys[] = $key;
- }
- // Top-level attachment
- self::$itemKeys[] = API::createAttachmentItem("imported_file", [
- "title" => array_shift($titles)
- ], false, null, 'key');
- // Top-level note
- self::$itemKeys[] = API::createNoteItem(array_shift($titles), false, null, 'key');
-
- //
- // Searches
- //
- /*for ($i=0; $i<5; $i++) {
- self::$searchKeys[] = API::createSearch("Test", 'default', null, 'key');
- }*/
- }
-
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testSortTopItemsTitle() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys&order=title"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $titles = self::$titles;
- asort($titles);
- $this->assertCount(sizeOf($titles), $keys);
- $correct = [];
- foreach ($titles as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- $this->assertEquals($correct, $keys);
- }
-
-
- public function testSortTopItemsCreator() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?key=" . self::$config['apiKey'] . "&format=keys&order=creator"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $names = self::$names;
- uasort($names, function ($a, $b) {
- if ($a === '' && $b !== '') return 1;
- if ($b === '' && $a !== '') return -1;
- return strcmp($a, $b);
- });
- $this->assertCount(sizeOf($names), $keys);
- $endKeys = array_splice($keys, -2);
- $correct = [];
- foreach ($names as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- // Remove empty names
- array_splice($correct, -2);
- $this->assertEquals($correct, $keys);
- // Check attachment and note, which should fall back to ordered added (itemID)
- $this->assertEquals(array_slice(self::$itemKeys, -2), $endKeys);
- }
-}
diff --git a/tests/remote/tests/API/2/StorageAdminTest.php b/tests/remote/tests/API/2/StorageAdminTest.php
deleted file mode 100644
index 33288d76..00000000
--- a/tests/remote/tests/API/2/StorageAdminTest.php
+++ /dev/null
@@ -1,113 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/bootstrap.inc.php';
-
-class StorageAdminTests extends APITests {
- const DEFAULT_QUOTA = 300;
-
- private static $toDelete = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- }
-
- public function setUp() {
- parent::setUp();
-
- // Clear subscription
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- 'quota=0&expiration=0',
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(self::DEFAULT_QUOTA, (int) $xml->quota);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- // Clear subscription
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- 'quota=0&expiration=0',
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
-
- public function test2GB() {
- $quota = 2000;
- $expiration = time() + (86400 * 365);
-
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- "quota=$quota&expiration=$expiration",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals($quota, (int) $xml->quota);
- $this->assertEquals($expiration, (int) $xml->expiration);
- }
-
-
- public function testUnlimited() {
- $quota = 'unlimited';
- $expiration = time() + (86400 * 365);
-
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- "quota=$quota&expiration=$expiration",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals($quota, (string) $xml->quota);
- $this->assertEquals($expiration, (int) $xml->expiration);
- }
-}
diff --git a/tests/remote/tests/API/2/TagTest.php b/tests/remote/tests/API/2/TagTest.php
deleted file mode 100644
index 7fdecc61..00000000
--- a/tests/remote/tests/API/2/TagTest.php
+++ /dev/null
@@ -1,385 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class TagTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
- public function setUp() {
- parent::setUp();
- API::useAPIVersion(2);
- }
-
- public function testEmptyTag() {
- $json = API::getItemTemplate("book");
- $json->tags[] = array(
- "tag" => "",
- "type" => 1
- );
-
- $response = API::postItem($json);
- $this->assert400ForObject($response, "Tag cannot be empty");
- }
-
-
- public function testInvalidTagObject() {
- $json = API::getItemTemplate("book");
- $json->tags[] = array("invalid");
-
- $response = API::postItem($json);
- $this->assert400ForObject($response, "Tag must be an object");
- }
-
-
- public function testItemTagSearch() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- $key1 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this, 'key');
-
- $key2 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this, 'key');
-
- //
- // Searches
- //
-
- // a (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=a"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // a and c (#2)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=a&tag=c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key2, $keys);
-
- // b and c (none)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=b&tag=c"
- );
- $this->assert200($response);
- $this->assertEmpty(trim($response->getBody()));
-
- // b or c (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=b%20||%20c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // a or b or c (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=a%20||%20b%20||%20c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // not a (none)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=-a"
- );
- $this->assert200($response);
- $this->assertEmpty(trim($response->getBody()));
-
- // not b (#2)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=-b"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key2, $keys);
-
- // (b or c) and a (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=b%20||%20c&tag=a"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // not nonexistent (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=-z"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // A (case-insensitive search)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&"
- . "tag=B"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key1, $keys);
- }
-
-
- public function testTagSearch() {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
-
- $itemKey1 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'key');
-
- $itemKey2 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags2)
- ), $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&content=json&tag=" . implode("%20||%20", $tags1)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($tags1), $response);
- }
-
-
- public function testTagNewer() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this);
-
- $version = API::getLibraryVersion();
-
- // 'newer' shouldn't return any results
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey'] . "&content=json&newer=$version"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
-
- // Create another item with tags
- API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this);
-
- // 'newer' should return new tag
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey'] . "&content=json&newer=$version"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $this->assertGreaterThan($version, $response->getHeader('Last-Modified-Version'));
- $content = API::getContentFromResponse($response);
- $json = json_decode($content, true);
- $this->assertEquals("c", $json['tag']);
- $this->assertEquals(0, $json['type']);
- }
-
-
- public function testMultiTagDelete() {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
- $tags3 = array("Foo");
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'key');
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag, "type" => 1);
- }, $tags2)
- ), $this, 'key');
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags3)
- ), $this, 'key');
-
- $libraryVersion = API::getLibraryVersion();
-
- // Missing version header
- $response = API::userDelete(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&content=json&tag="
- . implode("%20||%20", array_merge($tags1, $tags2))
- );
- $this->assert428($response);
-
- // Outdated version header
- $response = API::userDelete(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&content=json&tag="
- . implode("%20||%20", array_merge($tags1, $tags2)),
- array("If-Unmodified-Since-Version: " . ($libraryVersion - 1))
- );
- $this->assert412($response);
-
- // Delete
- $response = API::userDelete(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&content=json&tag="
- . implode("%20||%20", array_merge($tags1, $tags2)),
- array("If-Unmodified-Since-Version: $libraryVersion")
- );
- $this->assert204($response);
-
- // Make sure they're gone
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&content=json&tag="
- . implode("%20||%20", array_merge($tags1, $tags2, $tags3))
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- }
-
-
- /**
- * When modifying a tag on an item, only the item itself should have its
- * version updated, not other items that had (and still have) the same tag
- */
- public function testTagAddItemVersionChange() {
- $data1 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this, 'data');
- $json1 = json_decode($data1['content'], true);
- $version1 = $data1['version'];
-
- $data2 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this, 'data');
- $json2 = json_decode($data2['content'], true);
- $version2 = $data2['version'];
-
- // Remove tag 'a' from item 1
- $json1['tags'] = array(
- array("tag" => "d"),
- array("tag" => "c")
- );
-
- $response = API::postItem($json1);
- $this->assert200($response);
-
- // Item 1 version should be one greater than last update
- $xml1 = API::getItemXML($json1['itemKey']);
- $data1 = API::parseDataFromAtomEntry($xml1);
- $this->assertEquals($version2 + 1, $data1['version']);
-
- // Item 2 version shouldn't have changed
- $xml2 = API::getItemXML($json2['itemKey']);
- $data2 = API::parseDataFromAtomEntry($xml2);
- $this->assertEquals($version2, $data2['version']);
- }
-}
diff --git a/tests/remote/tests/API/2/VersionTest.php b/tests/remote/tests/API/2/VersionTest.php
deleted file mode 100644
index a055ed7c..00000000
--- a/tests/remote/tests/API/2/VersionTest.php
+++ /dev/null
@@ -1,630 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv2;
-use API2 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api2.inc.php';
-
-class VersionTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testSingleObjectLastModifiedVersion() {
- $this->_testSingleObjectLastModifiedVersion('collection');
- $this->_testSingleObjectLastModifiedVersion('item');
- $this->_testSingleObjectLastModifiedVersion('search');
- }
-
-
- public function testMultiObjectLastModifiedVersion() {
- $this->_testMultiObjectLastModifiedVersion('collection');
- $this->_testMultiObjectLastModifiedVersion('item');
- $this->_testMultiObjectLastModifiedVersion('search');
- }
-
-
- public function testMultiObject304NotModified() {
- $this->_testMultiObject304NotModified('collection');
- $this->_testMultiObject304NotModified('item');
- $this->_testMultiObject304NotModified('search');
- $this->_testMultiObject304NotModified('tag');
- }
-
-
- public function testNewerAndVersionsFormat() {
- $this->_testNewerAndVersionsFormat('collection');
- $this->_testNewerAndVersionsFormat('item');
- $this->_testNewerAndVersionsFormat('search');
- }
-
-
- public function testUploadUnmodified() {
- $this->_testUploadUnmodified('collection');
- $this->_testUploadUnmodified('item');
- $this->_testUploadUnmodified('search');
- }
-
-
- public function testNewerTags() {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
-
- $data1 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'data');
-
- $data2 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags2)
- ), $this, 'data');
-
- // Only newly added tags should be included in newer,
- // not previously added tags or tags added to items
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&newer=" . $data1['version']
- );
- $this->assertNumResults(2, $response);
-
- // Deleting an item shouldn't update associated tag versions
- $response = API::userDelete(
- self::$config['userID'],
- "items/{$data1['key']}?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: " . $data1['version']
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&newer=" . $data1['version']
- );
- $this->assertNumResults(2, $response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&newer=" . $libraryVersion
- );
- $this->assertNumResults(0, $response);
- }
-
-
- private function _testSingleObjectLastModifiedVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $versionProp = $objectType . "Version";
-
- switch ($objectType) {
- case 'collection':
- $objectKey = API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $objectKey = API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $objectKey = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'key'
- );
- break;
- }
-
- // Make sure all three instances of the object version
- // (Last-Modified-Version, zapi:version, and the JSON
- // {$objectType}Version property match the library version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $objectVersion = $response->getHeader("Last-Modified-Version");
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
- $this->assertEquals($objectVersion, $json->$versionProp);
- $this->assertEquals($objectVersion, $data['version']);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $this->assertEquals($libraryVersion, $objectVersion);
-
- $this->_modifyJSONObject($objectType, $json);
-
- // No If-Unmodified-Since-Version or JSON version property
- unset($json->$versionProp);
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert428($response);
-
- // Out of date version
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "If-Unmodified-Since-Version: " . ($objectVersion - 1)
- )
- );
- $this->assert412($response);
-
- // Update with version header
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json),
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert204($response);
- $newObjectVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($objectVersion, $newObjectVersion);
-
- // Update object with JSON version property
- $this->_modifyJSONObject($objectType, $json);
- $json->$versionProp = $newObjectVersion;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
- $newObjectVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($newObjectVersion, $newObjectVersion2);
-
- // Make sure new library version matches new object version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $this->assert200($response);
- $newLibraryVersion = $response->getHeader("Last-Modified-Version");
- $this->assertEquals($newObjectVersion2, $newLibraryVersion);
-
- // Create an item to increase the library version, and make sure
- // original object version stays the same
- API::createItem("book", array("title" => "Title"), $this, 'key');
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $this->assert200($response);
- $newObjectVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertEquals($newLibraryVersion, $newObjectVersion2);
-
- //
- // Delete object
- //
-
- // No If-Unmodified-Since-Version
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey']
- );
- $this->assert428($response);
-
- // Outdated If-Unmodified-Since-Version
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert412($response);
-
- // Delete object
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'],
- array(
- "If-Unmodified-Since-Version: " . $newObjectVersion2
- )
- );
- $this->assert204($response);
- }
-
-
- private function _modifyJSONObject($objectType, $json) {
- // Modifying object should increase its version
- switch ($objectType) {
- case 'collection':
- $json->name = "New Name " . uniqid();
- break;
-
- case 'item':
- $json->title = "New Title" . uniqid();
- break;
-
- case 'search':
- $json->name = "New Name" . uniqid();
- break;
- }
- }
-
-
- private function _testMultiObjectLastModifiedVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $objectKeyProp = $objectType . "Key";
- $objectVersionProp = $objectType . "Version";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
-
- switch ($objectType) {
- case 'collection':
- $json = new \stdClass();
- $json->name = "Name";
- break;
-
- case 'item':
- $json = API::getItemTemplate("book");
- break;
-
- case 'search':
- $json = new \stdClass();
- $json->name = "Name";
- $json->conditions = array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- );
- break;
- }
-
- // Outdated library version
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: " . ($version - 1)
- )
- );
- $this->assert412($response);
-
- // Make sure version didn't change during failure
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'] . "&limit=1"
- );
- $this->assertEquals($version, $response->getHeader("Last-Modified-Version"));
-
- // Create a new object, using library timestamp
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert200($response);
- $version2 = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version2));
- // Version should be incremented on new object
- $this->assertGreaterThan($version, $version2);
- $objectKey = API::getFirstSuccessKeyFromResponse($response);
-
- // Check single-object request
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assert200($response);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version2);
- $json = json_decode(API::getContentFromResponse($response));
-
- // Modify object
- $json->$objectKeyProp = $objectKey;
- switch ($objectType) {
- case 'collection':
- $json->name = "New Name";
- break;
-
- case 'item':
- $json->title = "New Title";
- break;
-
- case 'search':
- $json->name = "New Name";
- break;
- }
-
- // No If-Unmodified-Since-Version or object version property
- unset($json->$objectVersionProp);
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert428ForObject($response);
-
- // Outdated object version property
- $json->$objectVersionProp = $version - 1;
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert412ForObject($response, ucwords($objectType)
- . " has been modified since specified version "
- . "(expected {$json->$objectVersionProp}, found $version)");
-
- // Modify object, using object version property
- $json->$objectVersionProp = $version;
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- // Version should be incremented on modified object
- $version3 = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version3));
- $this->assertGreaterThan($version2, $version3);
-
- // Check library version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version3);
-
- // Check single-object request
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?key=" . self::$config['apiKey']
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version3);
-
- // TODO: Version should be incremented on deleted item
- }
-
-
- private function _testMultiObject304NotModified($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey'],
- array(
- "If-Modified-Since-Version: $version"
- )
- );
- $this->assert304($response);
- }
-
-
- private function _testNewerAndVersionsFormat($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $xmlArray = array();
-
- switch ($objectType) {
- case 'collection':
- $xmlArray[] = API::createCollection("Name", false, $this);
- $xmlArray[] = API::createCollection("Name", false, $this);
- $xmlArray[] = API::createCollection("Name", false, $this);
- break;
-
- case 'item':
- $xmlArray[] = API::createItem("book", array("title" => "Title"), $this);
- $xmlArray[] = API::createItem("book", array("title" => "Title"), $this);
- $xmlArray[] = API::createItem("book", array("title" => "Title"), $this);
- break;
-
-
- case 'search':
- $xmlArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this
- );
- $xmlArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this
- );
- $xmlArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this
- );
- }
-
- $objects = array();
- while ($xml = array_shift($xmlArray)) {
- $data = API::parseDataFromAtomEntry($xml);
- $objects[] = array(
- "key" => $data['key'],
- "version" => $data['version']
- );
- }
-
- $firstVersion = $objects[0]['version'];
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?key=" . self::$config['apiKey']
- . "&format=versions&newer=$firstVersion"
- );
-
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
-
- $keys = array_keys($json);
-
- $this->assertEquals($objects[2]['key'], array_shift($keys));
- $this->assertEquals($objects[2]['version'], array_shift($json));
- $this->assertEquals($objects[1]['key'], array_shift($keys));
- $this->assertEquals($objects[1]['version'], array_shift($json));
- $this->assertEmpty($json);
- }
-
-
- private function _testUploadUnmodified($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $xml = API::createCollection("Name", false, $this);
- break;
-
- case 'item':
- $xml = API::createItem("book", array("title" => "Title"), $this);
- break;
-
- case 'search':
- $xml = API::createSearch("Name", 'default', $this);
- break;
- }
-
- $version = (int) array_get_first($xml->xpath('//atom:entry/zapi:version'));
- $this->assertNotEquals(0, $version);
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/{$data['key']}?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assert204($response);
- $this->assertEquals($version, $response->getHeader("Last-Modified-Version"));
-
- switch ($objectType) {
- case 'collection':
- $xml = API::getCollectionXML($data['key']);
- break;
-
- case 'item':
- $xml = API::getItemXML($data['key']);
- break;
-
- case 'search':
- $xml = API::getSearchXML($data['key']);
- break;
- }
- $data = API::parseDataFromAtomEntry($xml);
- $this->assertEquals($version, $data['version']);
- }
-}
diff --git a/tests/remote/tests/API/3/APITests.inc.php b/tests/remote/tests/API/3/APITests.inc.php
deleted file mode 100644
index 45e5122b..00000000
--- a/tests/remote/tests/API/3/APITests.inc.php
+++ /dev/null
@@ -1,252 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-require_once 'tests/API/APITests.inc.php';
-use API3 as API, \Exception, \SimpleXMLElement;
-require_once 'include/api3.inc.php';
-
-//
-// Helper functions
-//
-class APITests extends \APITests {
- protected static $config;
- protected static $nsZAPI;
- private $notificationHeader = 'zotero-debug-notifications';
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- self::$nsZAPI = 'http://zotero.org/ns/api';
-
- API::useAPIVersion(3);
-
- API::setKeyUserPermission(self::$config['apiKey'], 'notes', true);
- API::setKeyUserPermission(self::$config['apiKey'], 'write', true);
- }
-
-
- public function setUp() {
- parent::setUp();
- API::useAPIKey(self::$config['apiKey']);
- API::useAPIVersion(3);
- $this->apiVersion = 3;
- }
-
-
- public function test() {}
-
- public function __call($name, $arguments) {
- if (preg_match("/^assert([1-5][0-9]{2})$/", $name, $matches)) {
- $this->assertHTTPStatus($matches[1], $arguments[0]);
- // Check response body
- if (isset($arguments[1])) {
- $this->assertEquals($arguments[1], $arguments[0]->getBody());
- }
- return;
- }
- // assertNNNForObject($response, $message=false, $pos=0)
- if (preg_match("/^assert([1-5][0-9]{2}|Unchanged)ForObject$/", $name, $matches)) {
- $code = $matches[1];
- if ($arguments[0] instanceof \HTTP_Request2_Response) {
- $this->assert200($arguments[0]);
- $json = json_decode($arguments[0]->getBody(), true);
- }
- else if (is_string($arguments[0])) {
- $json = json_decode($arguments[0], true);
- }
- else {
- $json = $arguments[0];
- }
- $this->assertNotNull($json);
-
- if ($code == 'Unchanged') {
- $index = isset($arguments[1]) ? $arguments[1] : 0;
- }
- else {
- $expectedMessage = !empty($arguments[1]) ? $arguments[1] : false;
- $index = isset($arguments[2]) ? $arguments[2] : 0;
- }
-
- if ($code == 200) {
- $this->assertArrayHasKey('successful', $json);
- if (!isset($json['successful'][$index])) {
- var_dump($json);
- throw new Exception("Index $index not found in 'successful' object");
- }
- // Deprecated
- $this->assertArrayHasKey('success', $json);
- if (!isset($json['success'][$index])) {
- var_dump($json);
- throw new Exception("Index $index not found in success object");
- }
- if ($expectedMessage) {
- throw new Exception("Cannot check response message of object for HTTP $code");
- }
- }
- else if ($code == 'Unchanged') {
- try {
- $this->assertArrayHasKey('unchanged', $json);
- $this->assertArrayHasKey($index, $json['unchanged']);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- }
- else if ($code[0] == '4' || $code[0] == '5') {
- try {
- $this->assertArrayHasKey('failed', $json);
- $this->assertArrayHasKey($index, $json['failed']);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- try {
- $this->assertEquals($code, $json['failed'][$index]['code']);
- }
- catch (Exception $e) {
- var_dump($json);
- throw $e;
- }
- if ($expectedMessage) {
- $this->assertEquals($expectedMessage, $json['failed'][$index]['message']);
- }
- }
- else {
- throw new Exception("HTTP $code cannot be returned for an individual object");
- }
- return;
- }
- throw new Exception("Invalid function $name");
- }
-
-
- protected function assertHasResults($res) {
- $xml = $res->getBody();
- $xml = new SimpleXMLElement($xml);
-
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertNotEquals(0, (int) $zapiNodes->totalResults);
- $this->assertNotEquals(0, count($xml->entry));
- }
-
-
- protected function assertTotalResults($num, $response) {
- $this->assertTrue(is_numeric($response->getHeader('Total-Results')));
- $this->assertEquals($num, (int) $response->getHeader('Total-Results'));
- }
-
-
- protected function assertNumResults($num, $response) {
- $contentType = $response->getHeader('Content-Type');
- if ($contentType == 'application/json') {
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($num, count($json));
- }
- else if (strpos($contentType, 'text/plain') === 0) {
- $rows = array_filter(explode("\n", trim($response->getBody())));
- $this->assertEquals($num, count($rows));
- }
- else if ($contentType == 'application/atom+xml') {
- $xml = $response->getBody();
- $xml = new SimpleXMLElement($xml);
- $this->assertEquals($num, count($xml->entry));
- }
- else if ($contentType == 'application/x-bibtex') {
- $matched = preg_match_all('/^@[a-z]+{/m', $response->getBody());
- $this->assertEquals($num, $matched);
- }
- else {
- throw new Exception("Unknown content type '$contentType'");
- }
- }
-
-
- protected function assertNoResults($response) {
- $this->assertTotalResults(0, $response);
-
- $contentType = $response->getHeader('Content-Type');
- if ($contentType == 'application/json') {
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(0, count($json));
- }
- else if ($contentType == 'application/atom+xml') {
- $xml = new SimpleXMLElement($response->getBody());
- $zapiNodes = $xml->children(self::$nsZAPI);
- $this->assertEquals(0, count($xml->entry));
- }
- else {
- throw new Exception("Unknown content type '$contentType'");
- }
- }
-
-
- protected function assertLastModifiedVersion($expected, $response) {
- $this->assertSame(
- is_numeric($expected) ? (string) $expected : $expected,
- $response->getHeader('Last-Modified-Version')
- );
- }
-
-
- protected function assertCountNotifications($expected, $response) {
- $header = $response->getHeader($this->notificationHeader);
- try {
- if ($expected === 0) {
- $this->assertNull($header);
- }
- else {
- $this->assertNotNull($header);
- $this->assertCount($expected, json_decode(base64_decode($header), true));
- }
- }
- catch (Exception $e) {
- echo "\nHeaders: " . base64_decode($header) . "\n";
- throw $e;
- }
- }
-
-
- protected function assertHasNotification($notification, $response) {
- $header = $response->getHeader($this->notificationHeader);
- $this->assertNotNull($header);
- // Header contains a Base64-encode array of encoded JSON notifications
- $notifications = json_decode(base64_decode($header), true);
- try {
- $this->assertContains($notification, array_map(function ($x) {
- return json_decode($x, true);
- }, $notifications));
- }
- catch (Exception $e) {
- echo "\nHeaders: " . base64_decode($header) . "\n";
- throw $e;
- }
- }
-}
diff --git a/tests/remote/tests/API/3/AtomTest.php b/tests/remote/tests/API/3/AtomTest.php
deleted file mode 100644
index bb635503..00000000
--- a/tests/remote/tests/API/3/AtomTest.php
+++ /dev/null
@@ -1,189 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class AtomTests extends APITests {
- private static $items;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- // Create test data
- $key = API::createItem("book", array(
- "title" => "Title",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- )
- )
- ), null, 'key');
- self::$items[$key] = 'Last, First. Title, n.d. '
- . \Zotero_Utilities::formatJSON(json_decode('{"key":"","version":0,"itemType":"book","title":"Title","creators":[{"creatorType":"author","firstName":"First","lastName":"Last"}],"abstractNote":"","series":"","seriesNumber":"","volume":"","numberOfVolumes":"","edition":"","place":"","publisher":"","date":"","numPages":"","language":"","ISBN":"","shortTitle":"","url":"","accessDate":"","archive":"","archiveLocation":"","libraryCatalog":"","callNumber":"","rights":"","extra":"","tags":[],"collections":[],"relations":{},"dateAdded":"","dateModified":""}'))
- . ' ';
-
- $key = API::createItem("book", array(
- "title" => "Title 2",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- ), null, 'key');
- self::$items[$key] = 'Last, First. Title 2. Edited by Ed McEditor, n.d. '
- . \Zotero_Utilities::formatJSON(json_decode('{"key":"","version":0,"itemType":"book","title":"Title 2","creators":[{"creatorType":"author","firstName":"First","lastName":"Last"},{"creatorType":"editor","firstName":"Ed","lastName":"McEditor"}],"abstractNote":"","series":"","seriesNumber":"","volume":"","numberOfVolumes":"","edition":"","place":"","publisher":"","date":"","numPages":"","language":"","ISBN":"","shortTitle":"","url":"","accessDate":"","archive":"","archiveLocation":"","libraryCatalog":"","callNumber":"","rights":"","extra":"","tags":[],"collections":[],"relations":{},"dateAdded":"","dateModified":""}'))
- . ' ';
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testFeedURIs() {
- $userID = self::$config['userID'];
-
- $response = API::userGet(
- $userID,
- "items?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $links = $xml->xpath('/atom:feed/atom:link');
- $this->assertEquals(self::$config['apiURLPrefix'] . "users/$userID/items?format=atom", (string) $links[0]['href']);
-
- // 'order'/'sort' should turn into 'sort'/'direction'
- $response = API::userGet(
- $userID,
- "items?format=atom&order=dateModified&sort=asc"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $links = $xml->xpath('/atom:feed/atom:link');
- $this->assertEquals(self::$config['apiURLPrefix'] . "users/$userID/items?direction=asc&format=atom&sort=dateModified", (string) $links[0]['href']);
- }
-
-
- public function testTotalResults() {
- $response = API::userHead(
- self::$config['userID'],
- "items?format=atom"
- );
- $this->assert200($response);
- $this->assertTotalResults(sizeOf(self::$items), $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertTotalResults(sizeOf(self::$items), $response);
- // Make sure there's no totalResults tag
- $this->assertCount(0, $xml->xpath('/atom:feed/zapi:totalResults'));
- }
-
-
- public function testMultiContent() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$keyStr&content=bib,json"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertTotalResults(sizeOf($keys), $response);
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add namespace prefix (from )
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key], $content);
- }
- }
-
-
- public function testMultiContentCached() {
- self::testMultiContent();
- }
-
-
- public function testAcceptHeader() {
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- "Accept: application/atom+xml,application/rdf+xml,application/rss+xml,application/xml,text/xml,*/*"
- ]
- );
- $this->assertContentType('application/atom+xml', $response);
-
- // But format= should still override
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json",
- [
- "Accept: application/atom+xml,application/rdf+xml,application/rss+xml,application/xml,text/xml,*/*"
- ]
- );
- $this->assertContentType('application/json', $response);
- }
-}
-?>
diff --git a/tests/remote/tests/API/3/BibTest.php b/tests/remote/tests/API/3/BibTest.php
deleted file mode 100644
index 1d938116..00000000
--- a/tests/remote/tests/API/3/BibTest.php
+++ /dev/null
@@ -1,336 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class BibTests extends APITests {
- private static $items;
- private static $multiResponses = [];
- private static $styles = [
- "default",
- "apa",
- "https://www.zotero.org/styles/apa",
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl"
- ];
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- // Create test data
- $key = API::createItem("book", array(
- "title" => "Title",
- "date" => "January 1, 2014",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- )
- )
- ), null, 'key');
- self::$items[$key] = [
- 'json' => [
- "citation" => array(
- "default" => 'Last, Title.',
- "apa" => '(Last, 2014)',
- "https://www.zotero.org/styles/apa" => '(Last, 2014)',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]'
- ),
- "bib" => array(
- "default" => 'Last, First. Title, 2014.',
- "apa" => 'Last, F. (2014). Title.',
- "https://www.zotero.org/styles/apa" => 'Last, F. (2014). Title.',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]F. Last, Title. 2014.'
- )
- ],
- 'atom' => [
- "citation" => array(
- "default" => 'Last, Title. ',
- "apa" => '(Last, 2014) ',
- "https://www.zotero.org/styles/apa" => '(Last, 2014) ',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1] '
- ),
- "bib" => array(
- "default" => 'Last, First. Title, 2014. ',
- "apa" => 'Last, F. (2014). Title. ',
- "https://www.zotero.org/styles/apa" => 'Last, F. (2014). Title. ',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]F. Last, Title. 2014. '
- )
- ]
- ];
-
- $key = API::createItem("book", array(
- "title" => "Title 2",
- "date" => "June 24, 2014",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- ), null, 'key');
- self::$items[$key] = [
- 'json' => [
- "citation" => array(
- "default" => 'Last, Title 2.',
- "apa" => '(Last, 2014)',
- "https://www.zotero.org/styles/apa" => '(Last, 2014)',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]'
- ),
- "bib" => array(
- "default" => 'Last, First. Title 2. Edited by Ed McEditor, 2014.',
- "apa" => 'Last, F. (2014). Title 2. (E. McEditor, Ed.).',
- "https://www.zotero.org/styles/apa" => 'Last, F. (2014). Title 2. (E. McEditor, Ed.).',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]F. Last, Title 2. 2014.'
- )
- ],
- 'atom' => [
- "citation" => array(
- "default" => 'Last, Title 2. ',
- "apa" => '(Last, 2014) ',
- "https://www.zotero.org/styles/apa" => '(Last, 2014) ',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1] '
- ),
- "bib" => array(
- "default" => 'Last, First. Title 2. Edited by Ed McEditor, 2014. ',
- "apa" => 'Last, F. (2014). Title 2. (E. McEditor, Ed.). ',
- "https://www.zotero.org/styles/apa" => 'Last, F. (2014). Title 2. (E. McEditor, Ed.). ',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]F. Last, Title 2. 2014. '
- )
- ]
- ];
-
- self::$multiResponses = [
- "default" => 'Last, First. Title, 2014.———. Title 2. Edited by Ed McEditor, 2014.',
- "apa" => 'Last, F. (2014a). Title.Last, F. (2014b). Title 2. (E. McEditor, Ed.).',
- "https://www.zotero.org/styles/apa" => 'Last, F. (2014a). Title.Last, F. (2014b). Title 2. (E. McEditor, Ed.).',
- "https://raw.githubusercontent.com/citation-style-language/styles/master/ieee.csl" => '[1]F. Last, Title 2. 2014.[2]F. Last, Title. 2014.'
- ];
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- // JSON
- public function testIncludeCitationSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?include=citation"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($expected['json']['citation'][$style], $json['citation'], "Item: $key, style: $style");
- }
- }
- }
-
-
- // Atom
- public function testContentCitationSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=citation"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $content = API::getContentFromResponse($response);
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString($expected['atom']['citation'][$style], $content);
- }
- }
- }
-
-
- // JSON
- public function testIncludeCitationMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$keyStr&include=citation"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $this->assertTotalResults(sizeOf($keys), $response);
- $json = API::getJSONFromResponse($response);
-
- foreach ($json as $item) {
- $key = $item['key'];
- $content = $item['citation'];
-
- $this->assertEquals(self::$items[$key]['json']['citation'][$style], $content);
- }
- }
- }
-
-
- // Atom
- public function testContentCitationMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$keyStr&content=citation"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $this->assertTotalResults(sizeOf($keys), $response);
- $xml = API::getXMLFromResponse($response);
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key]['atom']['citation'][$style], $content);
- }
- }
- }
-
-
- // JSON
- public function testIncludeBibSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?include=bib"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertXmlStringEqualsXmlString($expected['json']['bib'][$style], $json['bib']);
- }
- }
- }
-
-
- // Atom
- public function testContentBibSingle() {
- foreach (self::$styles as $style) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=bib"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $content = API::getContentFromResponse($response);
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString($expected['atom']['bib'][$style], $content);
- }
- }
- }
-
-
- // JSON
- public function testIncludeBibMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$keyStr&include=bib"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $this->assertTotalResults(sizeOf($keys), $response);
- $json = API::getJSONFromResponse($response);
-
- foreach ($json as $item) {
- $key = $item['key'];
- $this->assertXmlStringEqualsXmlString(self::$items[$key]['json']['bib'][$style], $item['bib']);
- }
- }
- }
-
-
- // Atom
- public function testContentBibMulti() {
- $keys = array_keys(self::$items);
- $keyStr = implode(',', $keys);
-
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$keyStr&content=bib"
- . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertTotalResults(sizeOf($keys), $response);
-
- $entries = $xml->xpath('//atom:entry');
- foreach ($entries as $entry) {
- $key = (string) $entry->children("http://zotero.org/ns/api")->key;
- $content = $entry->content->asXML();
-
- // Add zapi namespace
- $content = str_replace('assertXmlStringEqualsXmlString(self::$items[$key]['atom']['bib'][$style], $content);
- }
- }
- }
-
-
- public function testFormatBibMultiple() {
- foreach (self::$styles as $style) {
- $response = API::userGet(
- self::$config['userID'],
- "items?format=bib" . ($style == "default" ? "" : "&style=" . urlencode($style))
- );
- $this->assert200($response);
- $this->assertXmlStringEqualsXmlString(self::$multiResponses[$style], $response->getBody());
- }
- }
-}
diff --git a/tests/remote/tests/API/3/CacheTest.php b/tests/remote/tests/API/3/CacheTest.php
deleted file mode 100644
index fad5c2b4..00000000
--- a/tests/remote/tests/API/3/CacheTest.php
+++ /dev/null
@@ -1,77 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class CacheTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- /**
- * An object type's primary data cache for a library has to be created before
- *
- */
- public function testCacheCreatorPrimaryData() {
- $data = array(
- "title" => "Title",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- );
-
- $key = API::createItem("book", $data, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=csljson"
- );
- $json = json_decode(API::getContentFromResponse($response));
- $this->assertEquals("First", $json->author[0]->given);
- $this->assertEquals("Last", $json->author[0]->family);
- $this->assertEquals("Ed", $json->editor[0]->given);
- $this->assertEquals("McEditor", $json->editor[0]->family);
- }
-}
diff --git a/tests/remote/tests/API/3/CollectionTest.php b/tests/remote/tests/API/3/CollectionTest.php
deleted file mode 100644
index 5e68fa3e..00000000
--- a/tests/remote/tests/API/3/CollectionTest.php
+++ /dev/null
@@ -1,473 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class CollectionTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewCollection() {
- $name = "Test Collection";
- $json = API::createCollection($name, false, $this, 'json');
- $this->assertEquals($name, (string) $json['data']['name']);
- return $json['key'];
- }
-
-
- /**
- * @depends testNewCollection
- */
- public function testNewSubcollection($parent) {
- $name = "Test Subcollection";
-
- $json = API::createCollection($name, $parent, $this, 'json');
- $this->assertEquals($name, (string) $json['data']['name']);
- $this->assertEquals($parent, (string) $json['data']['parentCollection']);
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$parent"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(1, (int) $json['meta']['numCollections']);
- }
-
-
- public function testNewMultipleCollections() {
- $json = API::createCollection("Test Collection 1", false, $this, 'jsonData');
-
- $name1 = "Test Collection 2";
- $name2 = "Test Subcollection";
- $parent2 = $json['key'];
-
- $json = [
- [
- 'name' => $name1
- ],
- [
- 'name' => $name2,
- 'parentCollection' => $parent2
- ]
- ];
-
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['successful']);
- // Deprecated
- $this->assertCount(2, $json['success']);
-
- // Check data in write response
- $this->assertEquals($json['successful'][0]['key'], $json['successful'][0]['data']['key']);
- $this->assertEquals($json['successful'][1]['key'], $json['successful'][1]['data']['key']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['data']['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['data']['version']);
- $this->assertEquals($name1, $json['successful'][0]['data']['name']);
- $this->assertEquals($name2, $json['successful'][1]['data']['name']);
- $this->assertFalse($json['successful'][0]['data']['parentCollection']);
- $this->assertEquals($parent2, $json['successful'][1]['data']['parentCollection']);
-
- // Check in separate request, to be safe
- $keys = array_map(function ($o) {
- return $o['key'];
- }, $json['successful']);
- $response = API::getCollectionResponse($keys);
- $this->assertTotalResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($name1, $json[0]['data']['name']);
- $this->assertFalse($json[0]['data']['parentCollection']);
- $this->assertEquals($name2, $json[1]['data']['name']);
- $this->assertEquals($parent2, $json[1]['data']['parentCollection']);
- }
-
-
- public function testCreateKeyedCollections() {
- require_once '../../model/ID.inc.php';
- $key1 = \Zotero_ID::getKey();
- $name1 = "Test Collection 2";
- $name2 = "Test Subcollection";
-
- $json = [
- [
- 'key' => $key1,
- 'version' => 0,
- 'name' => $name1
- ],
- [
- 'name' => $name2,
- 'parentCollection' => $key1
- ]
- ];
-
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode($json),
- ["Content-Type: application/json"]
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['successful']);
-
- // Check data in write response
- $this->assertEquals($json['successful'][0]['key'], $json['successful'][0]['data']['key']);
- $this->assertEquals($json['successful'][1]['key'], $json['successful'][1]['data']['key']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['data']['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['data']['version']);
- $this->assertEquals($name1, $json['successful'][0]['data']['name']);
- $this->assertEquals($name2, $json['successful'][1]['data']['name']);
- $this->assertFalse($json['successful'][0]['data']['parentCollection']);
- $this->assertEquals($key1, $json['successful'][1]['data']['parentCollection']);
-
- // Check in separate request, to be safe
- $keys = array_map(function ($o) {
- return $o['key'];
- }, $json['successful']);
- $response = API::getCollectionResponse($keys);
- $this->assertTotalResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($name1, $json[0]['data']['name']);
- $this->assertFalse($json[0]['data']['parentCollection']);
- $this->assertEquals($name2, $json[1]['data']['name']);
- $this->assertEquals($key1, $json[1]['data']['parentCollection']);
- }
-
-
- public function testUpdateMultipleCollections() {
- $collection1Data = API::createCollection("Test 1", false, $this, 'jsonData');
- $collection2Name = "Test 2";
- $collection2Data = API::createCollection($collection2Name, false, $this, 'jsonData');
-
- $libraryVersion = API::getLibraryVersion();
-
- // Update with no change, which should still update library version (for now)
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([
- $collection1Data,
- $collection2Data
- ]),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert200($response);
- // If this behavior changes, remove the pre-increment
- $this->assertEquals(++$libraryVersion, $response->getHeader("Last-Modified-Version"));
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['unchanged']);
-
- $this->assertEquals($libraryVersion, API::getLibraryVersion());
-
- // Update
- $collection1NewName = "Test 1 Modified";
- $collection2NewParentKey = API::createCollection("Test 3", false, $this, 'key');
-
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([
- [
- 'key' => $collection1Data['key'],
- 'version' => $collection1Data['version'],
- 'name' => $collection1NewName
- ],
- [
- 'key' => $collection2Data['key'],
- 'version' => $collection2Data['version'],
- 'parentCollection' => $collection2NewParentKey
- ]
- ]),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['successful']);
- // Deprecated
- $this->assertCount(2, $json['success']);
-
- // Check data in write response
- $this->assertEquals($json['successful'][0]['key'], $json['successful'][0]['data']['key']);
- $this->assertEquals($json['successful'][1]['key'], $json['successful'][1]['data']['key']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['data']['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['data']['version']);
- $this->assertEquals($collection1NewName, $json['successful'][0]['data']['name']);
- $this->assertEquals($collection2Name, $json['successful'][1]['data']['name']);
- $this->assertFalse($json['successful'][0]['data']['parentCollection']);
- $this->assertEquals($collection2NewParentKey, $json['successful'][1]['data']['parentCollection']);
-
- // Check in separate request, to be safe
- $keys = array_map(function ($o) {
- return $o['key'];
- }, $json['successful']);
- $response = API::getCollectionResponse($keys);
- $this->assertTotalResults(2, $response);
- $json = API::getJSONFromResponse($response);
- // POST follows PATCH behavior, so unspecified values shouldn't change
- $this->assertEquals($collection1NewName, $json[0]['data']['name']);
- $this->assertFalse($json[0]['data']['parentCollection']);
- $this->assertEquals($collection2Name, $json[1]['data']['name']);
- $this->assertEquals($collection2NewParentKey, $json[1]['data']['parentCollection']);
- }
-
-
- public function testCollectionItemChange() {
- $collectionKey1 = API::createCollection('Test', false, $this, 'key');
- $collectionKey2 = API::createCollection('Test', false, $this, 'key');
-
- $json = API::createItem("book", array(
- 'collections' => array($collectionKey1)
- ), $this, 'json');
- $itemKey1 = $json['key'];
- $itemVersion1 = $json['version'];
- $this->assertEquals([$collectionKey1], $json['data']['collections']);
-
- $json = API::createItem("journalArticle", array(
- 'collections' => array($collectionKey2)
- ), $this, 'json');
- $itemKey2 = $json['key'];
- $itemVersion2 = $json['version'];
- $this->assertEquals([$collectionKey2], $json['data']['collections']);
-
- $json = API::getCollection($collectionKey1, $this);
- $this->assertEquals(1, $json['meta']['numItems']);
-
- $json = API::getCollection($collectionKey2, $this);
- $this->assertEquals(1, $json['meta']['numItems']);
- $collectionData2 = $json['data'];
-
- $libraryVersion = API::getLibraryVersion();
-
- // Add items to collection
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey1",
- json_encode(array(
- "collections" => array($collectionKey1, $collectionKey2)
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion1"
- )
- );
- $this->assert204($response);
-
- // Item version should change
- $json = API::getItem($itemKey1, $this);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- // Collection timestamp shouldn't change, but numItems should
- $json = API::getCollection($collectionKey2, $this);
- $this->assertEquals(2, $json['meta']['numItems']);
- $this->assertEquals($collectionData2['version'], $json['version']);
- $collectionData2 = $json['data'];
-
- $libraryVersion = API::getLibraryVersion();
-
- // Remove collections
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey2",
- json_encode(array(
- "collections" => array()
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion2"
- )
- );
- $this->assert204($response);
-
- // Item version should change
- $json = API::getItem($itemKey2, $this);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- // Collection timestamp shouldn't change, but numItems should
- $json = API::getCollection($collectionKey2, $this);
- $this->assertEquals(1, $json['meta']['numItems']);
- $this->assertEquals($collectionData2['version'], $json['version']);
-
- // Check collections arrays and numItems
- $json = API::getItem($itemKey1, $this);
- $this->assertCount(2, $json['data']['collections']);
- $this->assertContains($collectionKey1, $json['data']['collections']);
- $this->assertContains($collectionKey2, $json['data']['collections']);
-
- $json = API::getItem($itemKey2, $this);
- $this->assertCount(0, $json['data']['collections']);
-
- $json = API::getCollection($collectionKey1, $this);
- $this->assertEquals(1, $json['meta']['numItems']);
-
- $json = API::getCollection($collectionKey2, $this);
- $this->assertEquals(1, $json['meta']['numItems']);
- }
-
-
- public function testCollectionChildItemError() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $key = API::createItem("book", array(), $this, 'key');
- $json = API::createNoteItem("Test Note", $key, $this, 'jsonData');
- $json['collections'] = [$collectionKey];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert400($response);
- $this->assertEquals("Child items cannot be assigned to collections", $response->getBody());
- }
-
-
- public function testCollectionItems() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $json = API::createItem("book", ['collections' => [$collectionKey]], $this, 'jsonData');
- $itemKey1 = $json['key'];
- $itemVersion1 = $json['version'];
- $this->assertEquals([$collectionKey], $json['collections']);
-
- $json = API::createItem("journalArticle", ['collections' => [$collectionKey]], $this, 'jsonData');
- $itemKey2 = $json['key'];
- $itemVersion2 = $json['version'];
- $this->assertEquals([$collectionKey], $json['collections']);
-
- $childItemKey1 = API::createAttachmentItem("linked_url", [], $itemKey1, $this, 'key');
- $childItemKey2 = API::createAttachmentItem("linked_url", [], $itemKey2, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items?format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(4, $keys);
- $this->assertContains($itemKey1, $keys);
- $this->assertContains($itemKey2, $keys);
- $this->assertContains($childItemKey1, $keys);
- $this->assertContains($childItemKey2, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($itemKey1, $keys);
- $this->assertContains($itemKey2, $keys);
- }
-
- public function testCollectionItemMissingCollection() {
- $response = API::createItem("book", ['collections' => ["AAAAAAAA"]], $this, 'response');
- $this->assert409ForObject($response, "Collection AAAAAAAA not found");
- }
-
-
- public function test_should_return_409_on_missing_parent_collection() {
- $missingCollectionKey = "GDHRG8AZ";
- $json = API::createCollection("Test", [ 'parentCollection' => $missingCollectionKey ], $this);
- $this->assert409ForObject($json, "Parent collection $missingCollectionKey not found");
- $this->assertEquals($missingCollectionKey, $json['failed'][0]['data']['collection']);
- }
-
-
- public function test_should_return_413_if_collection_name_is_too_long() {
- $content = str_repeat("1", 256);
- $json = [
- "name" => $content
- ];
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject($response);
- }
-
-
- public function test_should_delete_collection_with_15_levels_below_it() {
- $json = API::createCollection("0", false, $this, 'json');
- $topCollectionKey = $json['key'];
- $parentCollectionKey = $topCollectionKey;
- for ($i = 0; $i < 15; $i++) {
- $json = API::createCollection("$i", $parentCollectionKey, $this, 'json');
- $parentCollectionKey = $json['key'];
- }
- $response = API::userDelete(
- self::$config['userID'],
- "collections?collectionKey=$topCollectionKey",
- [
- "If-Unmodified-Since-Version: {$json['version']}"
- ]
- );
- $this->assert204($response);
- }
-
-
- public function test_should_allow_emoji_in_name() {
- $name = "🐶"; // 4-byte character
- $json = API::createCollection($name, false, $this, 'json');
- $this->assertEquals($name, (string) $json['data']['name']);
- }
-}
-?>
diff --git a/tests/remote/tests/API/3/CreatorTest.php b/tests/remote/tests/API/3/CreatorTest.php
deleted file mode 100644
index 7c69a7c6..00000000
--- a/tests/remote/tests/API/3/CreatorTest.php
+++ /dev/null
@@ -1,194 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class CreatorTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testCreatorSummaryJSON() {
- $json = API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => "Test"
- )
- )
- ), $this, 'json');
- $itemKey = $json['key'];
-
- $this->assertEquals("Test", $json['meta']['creatorSummary']);
-
- $json = $json['data'];
- $json['creators'][] = [
- "creatorType" => "author",
- "firstName" => "Alice",
- "lastName" => "Foo"
- ];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- $json = API::getItem($itemKey, $this, 'json');
- $this->assertEquals("Test and Foo", $json['meta']['creatorSummary']);
-
- $json = $json['data'];
- $json['creators'][] = array(
- "creatorType" => "author",
- "firstName" => "Bob",
- "lastName" => "Bar"
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- $json = API::getItem($itemKey, $this, 'json');
- $this->assertEquals("Test et al.", $json['meta']['creatorSummary']);
- }
-
-
- public function testCreatorSummaryAtom() {
- $xml = API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => "Test"
- )
- )
- ), $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $itemKey = $data['key'];
- $json = json_decode($data['content'], true);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test", $creatorSummary);
-
- $json['creators'][] = array(
- "creatorType" => "author",
- "firstName" => "Alice",
- "lastName" => "Foo"
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($itemKey);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test and Foo", $creatorSummary);
-
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
-
- $json['creators'][] = array(
- "creatorType" => "author",
- "firstName" => "Bob",
- "lastName" => "Bar"
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($itemKey);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Test et al.", $creatorSummary);
- }
-
-
- public function testEmptyCreator() {
- $json = API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => chr(0xEF) . chr(0xBB) . chr(0xBF)
- )
- )
- ), $this, 'json');
- $this->assertArrayNotHasKey('creatorSummary', $json['meta']);
- }
-
-
- public function testCreatorCaseSensitivity() {
- API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => "SMITH"
- )
- )
- ), $this, 'json');
- $json = API::createItem("book", array(
- "creators" => array(
- array(
- "creatorType" => "author",
- "name" => "Smith"
- )
- )
- ), $this, 'json');
- $this->assertEquals('Smith', $json['data']['creators'][0]['name']);
- }
-
-
- public function test_should_allow_emoji_in_creator_name() {
- $char = "🐻"; // 4-byte character
- $json = API::createItem("book", [
- "creators" => [
- [
- "creatorType" => "author",
- "name" => $char
- ]
- ]
- ], $this, 'json');
- $this->assertEquals($char, $json['data']['creators'][0]['name']);
- }
-}
diff --git a/tests/remote/tests/API/3/ExportTest.php b/tests/remote/tests/API/3/ExportTest.php
deleted file mode 100644
index e4551e28..00000000
--- a/tests/remote/tests/API/3/ExportTest.php
+++ /dev/null
@@ -1,197 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class ExportTests extends APITests {
- private static $items;
- private static $multiResponses = [];
- private static $formats = ['bibtex', 'ris', 'csljson'];
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- // Create test data
- $key = API::createItem("book", array(
- "title" => "Title",
- "date" => "January 1, 2014",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- )
- )
- ), null, 'key');
- self::$items[$key] = [
- 'bibtex' => "\n@book{last_title_2014,\n title = {Title},\n author = {Last, First},\n month = jan,\n year = {2014}\n}",
- 'ris' => "TY - BOOK\r\nTI - Title\r\nAU - Last, First\r\nDA - 2014/01/01/\r\nPY - 2014\r\nER - \r\n\r\n",
- 'csljson' => [
- 'id' => self::$config['libraryID'] . "/$key",
- 'type' => 'book',
- 'title' => 'Title',
- 'author' => [
- [
- 'family' => 'Last',
- 'given' => 'First'
- ]
- ],
- 'issued' => [
- 'date-parts' => [
- ['2014', 1, 1]
- ]
- ]
- ]
- ];
-
- $key = API::createItem("book", array(
- "title" => "Title 2",
- "date" => "June 24, 2014",
- "creators" => array(
- array(
- "creatorType" => "author",
- "firstName" => "First",
- "lastName" => "Last"
- ),
- array(
- "creatorType" => "editor",
- "firstName" => "Ed",
- "lastName" => "McEditor"
- )
- )
- ), null, 'key');
- self::$items[$key] = [
- 'bibtex' => "\n@book{last_title_2014,\n title = {Title 2},\n author = {Last, First},\n editor = {McEditor, Ed},\n month = jun,\n year = {2014}\n}",
- 'ris' => "TY - BOOK\r\nTI - Title 2\r\nAU - Last, First\r\nA3 - McEditor, Ed\r\nDA - 2014/06/24/\r\nPY - 2014\r\nER - \r\n\r\n",
- 'csljson' => [
- 'id' => self::$config['libraryID'] . "/$key",
- 'type' => 'book',
- 'title' => 'Title 2',
- 'author' => [
- [
- 'family' => 'Last',
- 'given' => 'First'
- ]
- ],
- 'editor' => [
- [
- 'family' => 'McEditor',
- 'given' => 'Ed'
- ]
- ],
- 'issued' => [
- 'date-parts' => [
- ['2014', 6, 24]
- ]
- ]
- ]
- ];
-
- self::$multiResponses = [
- 'bibtex' => [
- "contentType" => "application/x-bibtex",
- "content" => "\n@book{last_title_2014,\n title = {Title 2},\n author = {Last, First},\n editor = {McEditor, Ed},\n month = jun,\n year = {2014}\n}\n\n@book{last_title_2014-1,\n title = {Title},\n author = {Last, First},\n month = jan,\n year = {2014}\n}",
- ],
- 'ris' => [
- "contentType" => "application/x-research-info-systems",
- "content" => "TY - BOOK\r\nTI - Title 2\r\nAU - Last, First\r\nA3 - McEditor, Ed\r\nDA - 2014/06/24/\r\nPY - 2014\r\nER - \r\n\r\nTY - BOOK\r\nTI - Title\r\nAU - Last, First\r\nDA - 2014/01/01/\r\nPY - 2014\r\nER - \r\n\r\n"
- ],
- 'csljson' => [
- "contentType" => "application/vnd.citationstyles.csl+json",
- "content" => [
- 'items' => [
- self::$items[array_keys(self::$items)[1]]['csljson'],
- self::$items[array_keys(self::$items)[0]]['csljson']
- ]
- ]
- ]
- ];
- }
-
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testExportInclude() {
- foreach (self::$formats as $format) {
- $response = API::userGet(
- self::$config['userID'],
- "items?include=$format"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- foreach ($json as $obj) {
- $this->assertEquals(self::$items[$obj['key']][$format], $obj[$format]);
- }
- }
- }
-
-
- public function testExportFormatSingle() {
- foreach (self::$formats as $format) {
- foreach (self::$items as $key => $expected) {
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?format=$format"
- );
- $this->assert200($response);
- $body = $response->getBody();
- if (is_array($expected[$format])) {
- $body = json_decode($body, true);
- }
- // TODO: Remove in APIv4
- if ($format == 'csljson') {
- $body = $body['items'][0];
- }
- $this->assertEquals($expected[$format], $body);
- }
- }
- }
-
-
- public function testExportFormatMultiple() {
- foreach (self::$formats as $format) {
- $response = API::userGet(
- self::$config['userID'],
- "items?format=$format"
- );
- $this->assert200($response);
- $this->assertContentType(self::$multiResponses[$format]['contentType'], $response);
- $body = $response->getBody();
- if (is_array(self::$multiResponses[$format]['content'])) {
- $body = json_decode($body, true);
- }
- $this->assertEquals(self::$multiResponses[$format]['content'], $body);
- }
- }
-}
diff --git a/tests/remote/tests/API/3/FileTest.php b/tests/remote/tests/API/3/FileTest.php
deleted file mode 100644
index 9128673d..00000000
--- a/tests/remote/tests/API/3/FileTest.php
+++ /dev/null
@@ -1,2014 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API, HTTP, SimpleXMLElement, Sync, Z_Tests;
-require_once 'APITests.inc.php';
-require_once 'include/bootstrap.inc.php';
-
-/**
- * @group s3
- */
-class FileTests extends APITests {
- private static $toDelete = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
-
- // Delete work files
- $delete = array("file", "old", "new", "patch");
- foreach ($delete as $file) {
- if (file_exists("work/$file")) {
- unlink("work/$file");
- }
- }
- clearstatcache();
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $s3Client = Z_Tests::$AWS->createS3();
-
- foreach (self::$toDelete as $file) {
- try {
- $s3Client->deleteObject([
- 'Bucket' => self::$config['s3Bucket'],
- 'Key' => $file
- ]);
- }
- catch (\Aws\S3\Exception\S3Exception $e) {
- if ($e->getAwsErrorCode() == 'NoSuchKey') {
- echo "\n$file not found on S3 to delete\n";
- }
- else {
- throw $e;
- }
- }
- }
- }
-
-
- public function testNewEmptyImportedFileAttachmentItem() {
- return API::createAttachmentItem("imported_file", [], false, $this, 'key');
- }
-
-
- /**
- * Test errors getting file upload authorization via form data
- *
- * @depends testNewEmptyImportedFileAttachmentItem
- */
- public function testAddFileFormDataAuthorizationErrors($parentKey) {
- $fileContents = self::getRandomUnicodeString();
- $hash = md5($fileContents);
- $mtime = time() * 1000;
- $size = strlen($fileContents);
- $filename = "test_" . $fileContents;
-
- $fileParams = array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => "text/plain",
- "charset" => "utf-8"
- );
-
- // Check required params
- foreach (array("md5", "filename", "filesize", "mtime") as $exclude) {
- $response = API::userPost(
- self::$config['userID'],
- "items/$parentKey/file",
- $this->implodeParams($fileParams, array($exclude)),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
- }
-
- // Seconds-based mtime
- $fileParams2 = $fileParams;
- $fileParams2['mtime'] = round($mtime / 1000);
- $response = API::userPost(
- self::$config['userID'],
- "items/$parentKey/file",
- $this->implodeParams($fileParams2),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- // TODO: Enable this test when the dataserver enforces it
- //$this->assert400($response);
- //$this->assertEquals('mtime must be specified in milliseconds', $response->getBody());
-
- $fileParams = $this->implodeParams($fileParams);
-
- // Invalid If-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$parentKey/file",
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . md5("invalidETag")
- )
- );
- $this->assert412($response);
-
- // Missing If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$parentKey/file",
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- )
- );
- $this->assert428($response);
-
- // Invalid If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$parentKey/file",
- $fileParams,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: invalidETag"
- )
- );
- $this->assert400($response);
- }
-
-
- public function testAddFileFormDataFull() {
- $parentKey = API::createItem("book", false, $this, 'key');
-
- $json = API::createAttachmentItem("imported_file", [], $parentKey, $this, 'json');
- $attachmentKey = $json['key'];
- $originalVersion = $json['version'];
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- self::$toDelete[] = "$hash";
-
- // Upload wrong contents to S3
- $response = HTTP::post(
- $json->url,
- $json->prefix . strrev($fileContents) . $json->suffix,
- [
- "Content-Type: " . $json->contentType
- ]
- );
- $this->assert400($response);
- $this->assertContains(
- "The Content-MD5 you specified did not match what we received.", $response->getBody()
- );
-
- // Upload to S3
- $response = HTTP::post(
- $json->url,
- $json->prefix . $fileContents . $json->suffix,
- [
- "Content-Type: " . $json->contentType
- ]
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // No If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- )
- );
- $this->assert428($response);
-
- // Invalid upload key
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- "upload=invalidUploadKey",
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey"
- );
- $json = API::getJSONFromResponse($response)['data'];
-
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($mtime, $json['mtime']);
- $this->assertEquals($contentType, $json['contentType']);
- $this->assertEquals($charset, $json['charset']);
-
- return array(
- "key" => $attachmentKey,
- "json" => $json,
- "size" => $size
- );
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileFormDataFullParams() {
- $json = API::createAttachmentItem("imported_file", [], false, $this, 'jsonData');
- $attachmentKey = $json['key'];
-
- // Get serverDateModified
- $serverDateModified = $json['dateAdded'];
- sleep(1);
-
- $originalVersion = $json['version'];
-
- // Get a sync timestamp from before the file is updated
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset,
- "params" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- self::$toDelete[] = "$hash";
-
- // Generate form-data -- taken from S3::getUploadPostData()
- $boundary = "---------------------------" . md5(uniqid());
- $prefix = "";
- foreach ($json->params as $key => $val) {
- $prefix .= "--$boundary\r\n"
- . "Content-Disposition: form-data; name=\"$key\"\r\n\r\n"
- . $val . "\r\n";
- }
- $prefix .= "--$boundary\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n";
- $suffix = "\r\n--$boundary--";
-
- // Upload to S3
- $response = HTTP::post(
- $json->url,
- $prefix . $fileContents . $suffix,
- array(
- "Content-Type: multipart/form-data; boundary=$boundary"
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/$attachmentKey/file",
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey"
- );
- $json = API::getJSONFromResponse($response)['data'];
-
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($mtime, $json['mtime']);
- $this->assertEquals($contentType, $json['contentType']);
- $this->assertEquals($charset, $json['charset']);
-
- // Make sure version has changed
- $this->assertNotEquals($originalVersion, $json['version']);
-
- // Make sure new attachment is passed via sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertGreaterThan(0, $xml->updated[0]->count());
- }
-
-
- /**
- * @depends testAddFileFormDataFull
- */
- public function testAddFileExisting($addFileData) {
- $key = $addFileData['key'];
- $json = $addFileData['json'];
- $md5 = $json['md5'];
- $size = $addFileData['size'];
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $json['md5'],
- "filename" => $json['filename'],
- "filesize" => $size,
- "mtime" => $json['mtime'],
- "contentType" => $json['contentType'],
- "charset" => $json['charset']
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . $json['md5']
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get upload authorization for existing file with different filename
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $json['md5'],
- "filename" => $json['filename'] . '等', // Unicode 1.1 character, to test signature generation
- "filesize" => $size,
- "mtime" => $json['mtime'],
- "contentType" => $json['contentType'],
- "charset" => $json['charset']
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . $json['md5']
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- return array(
- "key" => $key,
- "md5" => $md5,
- "filename" => $json['filename'] . '等'
- );
- }
-
-
- /**
- * @depends testAddFileExisting
- * @group attachments
- */
- public function testGetFile($addFileData) {
- $key = $addFileData['key'];
- $md5 = $addFileData['md5'];
- $filename = $addFileData['filename'];
-
- // Get in view mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file/view"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertRegExp('/^https:\/\/[^\/]+\/[0-9]+\//', $location);
- $filenameEncoded = rawurlencode($filename);
- $this->assertEquals($filenameEncoded, substr($location, -1 * strlen($filenameEncoded)));
-
- // Get from view mode
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($md5, md5($response->getBody()));
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($md5, md5($response->getBody()));
-
- return array(
- "key" => $key,
- "response" => $response
- );
- }
-
-
- /**
- * @depends testGetFile
- * @group classic-sync
- */
- public function testAddFilePartial($getFileData) {
- // Get serverDateModified
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}"
- );
- $json = API::getJSONFromResponse($response)['data'];
- $serverDateModified = $json['dateModified'];
- sleep(1);
-
- $originalVersion = $json['version'];
-
- // Get a sync timestamp from before the file is updated
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- $oldFilename = "work/old";
- $fileContents = $getFileData['response']->getBody();
- file_put_contents($oldFilename, $fileContents);
-
- $newFilename = "work/new";
- $patchFilename = "work/patch";
-
- $algorithms = array(
- "bsdiff" => "bsdiff "
- . escapeshellarg($oldFilename) . " "
- . escapeshellarg($newFilename) . " "
- . escapeshellarg($patchFilename),
- "xdelta" => "xdelta3 -f -e -9 -S djw -s "
- . escapeshellarg($oldFilename) . " "
- . escapeshellarg($newFilename) . " "
- . escapeshellarg($patchFilename),
- "vcdiff" => "vcdiff encode "
- . "-dictionary " . escapeshellarg($oldFilename) . " "
- . " -target " . escapeshellarg($newFilename) . " "
- . " -delta " . escapeshellarg($patchFilename)
- );
-
- foreach ($algorithms as $algo => $cmd) {
- clearstatcache();
-
- // Create random contents
- file_put_contents($newFilename, uniqid(self::getRandomUnicodeString(), true));
- $newHash = md5_file($newFilename);
-
- // Get upload authorization
- $fileParams = array(
- "md5" => $newHash,
- "filename" => "test_" . $fileContents,
- "filesize" => filesize($newFilename),
- "mtime" => filemtime($newFilename) * 1000,
- "contentType" => "text/plain",
- "charset" => "utf-8"
- );
- $response = API::userPost(
- self::$config['userID'],
- "items/{$getFileData['key']}/file",
- $this->implodeParams($fileParams),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . md5_file($oldFilename)
- )
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- exec($cmd, $output, $ret);
- if ($ret != 0) {
- echo "Warning: Error running $algo -- skipping file upload test\n";
- continue;
- }
-
- $patch = file_get_contents($patchFilename);
- $this->assertNotEquals("", $patch);
-
- self::$toDelete[] = "$newHash";
-
- // Upload patch file
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$getFileData['key']}/file?algorithm=$algo&upload=" . $json->uploadKey,
- $patch,
- array(
- "If-Match: " . md5_file($oldFilename)
- )
- );
- $this->assert204($response);
-
- unlink($patchFilename);
- rename($newFilename, $oldFilename);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}"
- );
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($fileParams['md5'], $json['md5']);
- $this->assertEquals($fileParams['mtime'], $json['mtime']);
- $this->assertEquals($fileParams['contentType'], $json['contentType']);
- $this->assertEquals($fileParams['charset'], $json['charset']);
-
- // Make sure version has changed
- $this->assertNotEquals($originalVersion, $json['version']);
-
- // Make sure new attachment is passed via sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertGreaterThan(0, $xml->updated[0]->count());
-
- // Verify file on S3
- $response = API::userGet(
- self::$config['userID'],
- "items/{$getFileData['key']}/file"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
-
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileParams['md5'], md5($response->getBody()));
- $t = $fileParams['contentType'];
- $this->assertEquals(
- $t . (($t && $fileParams['charset']) ? "; charset={$fileParams['charset']}" : ""),
- $response->getHeader("Content-Type")
- );
- }
- }
-
-
- public function testExistingFileWithOldStyleFilename() {
- $fileContents = self::getRandomUnicodeString();
- $hash = md5($fileContents);
- $filename = 'test.txt';
- $size = strlen($fileContents);
-
- $parentKey = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_file", [], $parentKey, $this, 'jsonData');
- $key = $json['key'];
- $originalVersion = $json['version'];
- $mtime = time() * 1000;
- $contentType = 'text/plain';
- $charset = 'utf-8';
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
-
- // Upload to old-style location
- self::$toDelete[] = "$hash/$filename";
- self::$toDelete[] = "$hash";
- $s3Client = Z_Tests::$AWS->createS3();
- $s3Client->putObject([
- 'Bucket' => self::$config['s3Bucket'],
- 'Key' => $hash . '/' . $filename,
- 'Body' => $fileContents
- ]);
-
- // Register upload
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json->uploadKey,
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert204($response);
-
- // The file should be accessible on the item at the old-style location
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
-
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})/' . $filename . '\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get upload authorization for the same file and filename on another item, which should
- // result in 'exists', even though we uploaded to the old-style location
- $parentKey = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_file", [], $parentKey, $this, 'jsonData');
- $key = $json['key'];
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})/' . $filename . '\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileContents, $response->getBody());
- $this->assertEquals($contentType . '; charset=' . $charset, $response->getHeader('Content-Type'));
-
- // Get upload authorization for the same file and different filename on another item,
- // which should result in 'exists' and a copy of the file to the hash-only location
- $parentKey = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_file", [], $parentKey, $this, 'jsonData');
- $key = $json['key'];
- // Also use a different content type
- $contentType = 'application/x-custom';
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => "test2.txt",
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert200($response);
- $postJSON = json_decode($response->getBody());
- $this->assertNotNull($postJSON);
- $this->assertEquals(1, $postJSON->exists);
-
- // Get in download mode
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file"
- );
- $this->assert302($response);
- $location = $response->getHeader("Location");
- $this->assertEquals(1, preg_match('"^https://'
- // bucket.s3.amazonaws.com or s3.amazonaws.com/bucket
- . '(?:[^/]+|.+' . self::$config['s3Bucket'] . ')'
- . '/([a-f0-9]{32})\?"', $location, $matches));
- $this->assertEquals($hash, $matches[1]);
-
- // Get from S3
- $response = HTTP::get($location);
- $this->assert200($response);
- $this->assertEquals($fileContents, $response->getBody());
- $this->assertEquals($contentType, $response->getHeader('Content-Type'));
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClientV4() {
- API::userClear(self::$config['userID']);
-
- $fileContentType = "text/html";
- $fileCharset = "utf-8";
-
- $auth = array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- );
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $json = API::createAttachmentItem("imported_file", [], false, $this, 'jsonData');
- $originalVersion = $json['version'];
- $json['contentType'] = $fileContentType;
- $json['charset'] = $fileCharset;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $originalVersion = $response->getHeader("Last-Modified-Version");
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- // Get file info
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1&info=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $xml = new SimpleXMLElement($response->getBody());
-
- self::$toDelete[] = "$hash";
-
- $boundary = "---------------------------" . rand();
- $postData = "";
- foreach ($xml->params->children() as $key => $val) {
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"$key\"\r\n\r\n$val\r\n";
- }
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"file\"\r\n\r\n" . $fileContents . "\r\n";
- $postData .= "--" . $boundary . "--";
-
- // Upload to S3
- $response = HTTP::post(
- (string) $xml->url,
- $postData,
- array(
- "Content-Type: multipart/form-data; boundary=" . $boundary
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // Invalid upload key
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- "update=invalidUploadKey&mtime=" . $mtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert400($response);
-
- // No mtime
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert500($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key . "&mtime=" . $mtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}"
- );
- $json = API::getJSONFromResponse($response)['data'];
-
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($mtime, $json['mtime']);
-
- // Make sure attachment item wasn't updated (or else the client
- // will get a conflict when it tries to update the metadata)
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- )
- );
- $this->assert200($response);
- $mtime = $response->getBody();
- $this->assertRegExp('/^[0-9]{10}$/', $mtime);
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime + 1000
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // File exists with different filename
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename . '等', // Unicode 1.1 character, to test signature generation
- "filesize" => $size,
- "mtime" => $mtime + 1000
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // Make sure attachment item still wasn't updated
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- // Get attachment
- $xml = Sync::updated($sessionID, 2);
- $this->assertEquals(1, $xml->updated[0]->items->count());
- $itemXML = $xml->xpath("//updated/items/item[@key='" . $json['key'] . "']")[0];
- $this->assertEquals($fileContentType, (string) $itemXML['mimeType']);
- $this->assertEquals($fileCharset, (string) $itemXML['charset']);
- $this->assertEquals($hash, (string) $itemXML['storageHash']);
- $this->assertEquals($mtime + 1000, (string) $itemXML['storageModTime']);
-
- Sync::logout($sessionID);
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClientV4Zip() {
- API::userClear(self::$config['userID']);
-
- $auth = array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- );
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $json = API::createItem("book", false, $this, 'jsonData');
- $key = $json['key'];
-
- $fileContentType = "text/html";
- $fileCharset = "UTF-8";
- $fileFilename = "file.html";
- $fileModtime = time();
-
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $key = $json['key'];
- $version = $json['version'];
- $json['contentType'] = $fileContentType;
- $json['charset'] = $fileCharset;
- $json['filename'] = $fileFilename;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- // Get file info
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1&info=1",
- array(),
- $auth
- );
- $this->assert404($response);
-
- $zip = new \ZipArchive();
- $file = "work/$key.zip";
-
- if ($zip->open($file, \ZIPARCHIVE::CREATE) !== TRUE) {
- throw new Exception("Cannot open ZIP file");
- }
-
- $zip->addFromString($fileFilename, self::getRandomUnicodeString());
- $zip->addFromString("file.css", self::getRandomUnicodeString());
- $zip->close();
-
- $hash = md5_file($file);
- $filename = $key . ".zip";
- $size = filesize($file);
- $fileContents = file_get_contents($file);
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $fileModtime,
- "zip" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $xml = new SimpleXMLElement($response->getBody());
-
- self::$toDelete[] = "$hash";
-
- $boundary = "---------------------------" . rand();
- $postData = "";
- foreach ($xml->params->children() as $key => $val) {
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"$key\"\r\n\r\n$val\r\n";
- }
- $postData .= "--" . $boundary . "\r\nContent-Disposition: form-data; "
- . "name=\"file\"\r\n\r\n" . $fileContents . "\r\n";
- $postData .= "--" . $boundary . "--";
-
- // Upload to S3
- $response = HTTP::post(
- (string) $xml->url,
- $postData,
- array(
- "Content-Type: multipart/form-data; boundary=" . $boundary
- )
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- "update=" . $xml->key . "&mtime=" . $fileModtime,
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert204($response);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}"
- );
- $json = API::getJSONFromResponse($response)['data'];
-
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($fileFilename, $json['filename']);
- $this->assertEquals($fileModtime, $json['mtime']);
-
- // Make sure attachment item wasn't updated (or else the client
- // will get a conflict when it tries to update the metadata)
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync?auth=1",
- array(),
- array(
- 'username' => self::$config['username'],
- 'password' => self::$config['password']
- )
- );
- $this->assert200($response);
- $mtime = $response->getBody();
- $this->assertRegExp('/^[0-9]{10}$/', $mtime);
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/{$json['key']}/file?auth=1&iskey=1&version=1",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $fileModtime + 1000,
- "zip" => 1
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded"
- ),
- $auth
- );
- $this->assert200($response);
- $this->assertContentType("application/xml", $response);
- $this->assertEquals(" ", $response->getBody());
-
- // Make sure attachment item still wasn't updated
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, $lastsync);
- Sync::logout($sessionID);
- $this->assertEquals(0, $xml->updated[0]->count());
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClientV5() {
- API::userClear(self::$config['userID']);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/html";
- $charset = "utf-8";
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync"
- );
- $this->assert404($response);
-
- $json = API::createAttachmentItem("imported_file", [
- 'contentType' => $contentType,
- 'charset' => $charset
- ], false, $this, 'jsonData');
- $key = $json['key'];
- $originalVersion = $json['version'];
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- // File shouldn't exist
- $response = API::userGet(
- self::$config['userID'],
- "items/$key/file"
- );
- $this->assert404($response);
-
- //
- // Get upload authorization
- //
-
- // Require If-Match/If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded"
- ]
- );
- $this->assert428($response, "If-Match/If-None-Match header not provided");
-
- // Get authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$hash";
-
- //
- // Upload to S3
- //
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $fileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // Require If-Match/If-None-Match
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded"
- ]
- );
- $this->assert428($response, "If-Match/If-None-Match header not provided");
-
- // Invalid upload key
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=invalidUploadKey",
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert400($response);
-
- // If-Match shouldn't match unregistered file
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $hash"
- ]
- );
- $this->assert412($response);
- $this->assertNull($response->getHeader("Last-Modified-Version"));
-
- // Successful registration
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert204($response);
- $newVersion = $response->getHeader('Last-Modified-Version');
- $this->assertGreaterThan($originalVersion, $newVersion);
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($mtime, $json['mtime']);
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($contentType, $json['contentType']);
- $this->assertEquals($charset, $json['charset']);
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync"
- );
- $this->assert200($response);
- $this->assertRegExp('/^[0-9]{10}$/', $response->getBody());
-
- //
- // Update file
- //
-
- // Conflict for If-None-Match when file exists
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime + 1000,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert412($response, "If-None-Match: * set but file exists");
- $this->assertNotNull($response->getHeader("Last-Modified-Version"));
-
- // Conflict for If-Match when existing file differs
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime + 1000,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: " . md5("invalid")
- ]
- );
- $this->assert412($response, "ETag does not match current version of file");
- $this->assertNotNull($response->getHeader("Last-Modified-Version"));
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime + 1000,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $hash"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey("exists", $json);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($newVersion, $version);
- $newVersion = $version;
-
- // File exists with different filename
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime + 1000,
- "filename" => $filename . '等', // Unicode 1.1 character, to test signature generation
- "filesize" => $size,
- "contentType" => $contentType,
- "charset" => $charset
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $hash"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey("exists", $json);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($newVersion, $version);
-
- // Get attachment via classic sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, 2);
- $this->assertEquals(1, $xml->updated[0]->items->count());
- $itemXML = $xml->xpath("//updated/items/item[@key='$key']")[0];
- $this->assertEquals($contentType, (string) $itemXML['mimeType']);
- $this->assertEquals($charset, (string) $itemXML['charset']);
- $this->assertEquals($hash, (string) $itemXML['storageHash']);
- $this->assertEquals($mtime + 1000, (string) $itemXML['storageModTime']);
- Sync::logout($sessionID);
- }
-
- /**
- * @group classic-sync
- */
- public function testAddFileClientV5Zip() {
- API::userClear(self::$config['userID']);
-
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/html";
- $charset = "utf-8";
- $filename = "file.html";
- $mtime = time();
- $hash = md5($fileContents);
-
-
- // Get last storage sync
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync"
- );
- $this->assert404($response);
-
- $json = API::createItem("book", false, $this, 'jsonData');
- $key = $json['key'];
-
- $json = API::createAttachmentItem("imported_url", [
- 'contentType' => $contentType,
- 'charset' => $charset
- ], $key, $this, 'jsonData');
- $key = $json['key'];
- $originalVersion = $json['version'];
-
- // Create ZIP file
- $zip = new \ZipArchive();
- $file = "work/$key.zip";
- if ($zip->open($file, \ZIPARCHIVE::CREATE) !== TRUE) {
- throw new Exception("Cannot open ZIP file");
- }
- $zip->addFromString($filename, $fileContents);
- $zip->addFromString("file.css", self::getRandomUnicodeString());
- $zip->close();
- $zipHash = md5_file($file);
- $zipFilename = $key . ".zip";
- $zipSize = filesize($file);
- $zipFileContents = file_get_contents($file);
-
- // Get a sync timestamp from before the file is updated
- sleep(1);
- require_once 'include/sync.inc.php';
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID);
- $lastsync = (int) $xml['timestamp'];
- Sync::logout($sessionID);
-
- //
- // Get upload authorization
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $zipSize,
- "zipMD5" => $zipHash,
- "zipFilename" => $zipFilename
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$zipHash";
-
- // Upload to S3
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $zipFileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
-
- // If-Match with file hash shouldn't match unregistered file
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $hash"
- ]
- );
- $this->assert412($response);
-
- // If-Match with ZIP hash shouldn't match unregistered file
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $zipHash"
- ]
- );
- $this->assert412($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert204($response);
- $newVersion = $response->getHeader("Last-Modified-Version");
-
- // Verify attachment item metadata
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($hash, $json['md5']);
- $this->assertEquals($mtime, $json['mtime']);
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($contentType, $json['contentType']);
- $this->assertEquals($charset, $json['charset']);
-
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync"
- );
- $this->assert200($response);
- $this->assertRegExp('/^[0-9]{10}$/', $response->getBody());
-
- // File exists
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime + 1000,
- "filename" => $filename,
- "filesize" => $zipSize,
- "zip" => 1,
- "zipMD5" => $zipHash,
- "zipFilename" => $zipFilename
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-Match: $hash"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey("exists", $json);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($newVersion, $version);
-
- // Get attachment via classic sync
- $sessionID = Sync::login();
- $xml = Sync::updated($sessionID, 2);
- $this->assertEquals(1, $xml->updated[0]->items->count());
- $itemXML = $xml->xpath("//updated/items/item[@key='$key']")[0];
- $this->assertEquals($contentType, (string) $itemXML['mimeType']);
- $this->assertEquals($charset, (string) $itemXML['charset']);
- $this->assertEquals($hash, (string) $itemXML['storageHash']);
- $this->assertEquals($mtime + 1000, (string) $itemXML['storageModTime']);
- Sync::logout($sessionID);
- }
-
-
- public function testClientV5ShouldRejectFileSizeMismatch() {
- API::userClear(self::$config['userID']);
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/plain";
- $charset = "utf-8";
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = 0;
-
- $json = API::createAttachmentItem("imported_file", [
- 'contentType' => $contentType,
- 'charset' => $charset
- ], false, $this, 'jsonData');
- $key = $json['key'];
- $originalVersion = $json['version'];
-
- // Get authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$hash";
-
- // Try to upload to S3, which should fail
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $fileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert400($response);
- $this->assertContains(
- "Your proposed upload exceeds the maximum allowed size", $response->getBody()
- );
- }
-
-
- public function testClientV5ShouldReturn404GettingAuthorizationForMissingFile() {
- // Get authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/UP24VFQR/file",
- $this->implodeParams([
- "md5" => md5('qzpqBjLddCc6UhfX'),
- "mtime" => 1477002989206,
- "filename" => 'test.pdf',
- "filesize" => 12345
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert404($response);
- }
-
-
- public function testAddFileLinkedAttachment() {
- $key = API::createAttachmentItem("linked_file", [], false, $this, 'key');
-
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
- $contentType = "text/plain";
- $charset = "utf-8";
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams(array(
- "md5" => $hash,
- "filename" => $filename,
- "filesize" => $size,
- "mtime" => $mtime,
- "contentType" => $contentType,
- "charset" => $charset
- )),
- array(
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- )
- );
- $this->assert400($response);
- }
-
-
- public function test_updating_attachment_hash_should_clear_associated_storage_file() {
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/html";
- $charset = "utf-8";
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
-
- $json = API::createAttachmentItem("imported_file", [
- 'contentType' => $contentType,
- 'charset' => $charset
- ], false, $this, 'jsonData');
- $itemKey = $json['key'];
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$itemKey/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$hash";
-
- // Upload to S3
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $fileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert201($response);
-
- // Register upload
- $response = API::userPost(
- self::$config['userID'],
- "items/$itemKey/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert204($response);
- $newVersion = $response->getHeader('Last-Modified-Version');
-
- $filename = "test.pdf";
- $mtime = time();
- $hash = md5(uniqid());
-
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey",
- json_encode([
- "filename" => $filename,
- "mtime" => $mtime,
- "md5" => $hash,
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $newVersion"
- ]
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$itemKey/file"
- );
- $this->assert404($response);
- }
-
-
- public function test_updating_compressed_attachment_hash_should_clear_associated_storage_file() {
- // Create initial file
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/html";
- $charset = "utf-8";
- $filename = "file.html";
- $mtime = time();
- $hash = md5($fileContents);
-
- $json = API::createAttachmentItem("imported_file", [
- 'contentType' => $contentType,
- 'charset' => $charset
- ], false, $this, 'jsonData');
- $itemKey = $json['key'];
-
- // Create initial ZIP file
- $zip = new \ZipArchive();
- $file = "work/$itemKey.zip";
- if ($zip->open($file, \ZIPARCHIVE::CREATE) !== TRUE) {
- throw new Exception("Cannot open ZIP file");
- }
- $zip->addFromString($filename, $fileContents);
- $zip->addFromString("file.css", self::getRandomUnicodeString());
- $zip->close();
- $zipHash = md5_file($file);
- $zipFilename = $itemKey . ".zip";
- $zipSize = filesize($file);
- $zipFileContents = file_get_contents($file);
-
- // Get upload authorization
- $response = API::userPost(
- self::$config['userID'],
- "items/$itemKey/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $zipSize,
- "zipMD5" => $zipHash,
- "zipFilename" => $zipFilename
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$zipHash";
-
- // Upload to S3
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $zipFileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert201($response);
-
- // Register upload
- $response = API::userPost(
- self::$config['userID'],
- "items/$itemKey/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert204($response);
- $newVersion = $response->getHeader('Last-Modified-Version');
-
- // Set new attachment file info
- $hash = md5(uniqid());
- $mtime = time();
- $zipHash = md5(uniqid());
- $zipSize++;
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey",
- json_encode([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $newVersion"
- ]
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$itemKey/file"
- );
- $this->assert404($response);
- }
-
-
- // TODO: Reject for keys not owned by user, even if public library
- public function testLastStorageSyncNoAuthorization() {
- API::useAPIKey(false);
- $response = API::userGet(
- self::$config['userID'],
- "laststoragesync"
- );
- $this->assert401($response);
- }
-
-
- private function implodeParams($params, $exclude=array()) {
- $parts = array();
- foreach ($params as $key => $val) {
- if (in_array($key, $exclude)) {
- continue;
- }
- $parts[] = $key . "=" . urlencode($val);
- }
- return implode("&", $parts);
- }
-
-
- private function getRandomUnicodeString() {
- return "Âéìøü 这是一个测试。 " . uniqid();
- }
-}
diff --git a/tests/remote/tests/API/3/FullTextTest.php b/tests/remote/tests/API/3/FullTextTest.php
deleted file mode 100644
index e088ab52..00000000
--- a/tests/remote/tests/API/3/FullTextTest.php
+++ /dev/null
@@ -1,447 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-/**
- * @group fulltext
- */
-class FullTextTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testVersionsAnonymous() {
- API::useAPIKey(false);
- $response = API::userGet(
- self::$config['userID'],
- "fulltext"
- );
- $this->assert403($response);
- }
-
-
- public function testContentAnonymous() {
- API::useAPIKey(false);
- $response = API::userGet(
- self::$config['userID'],
- "items/AAAAAAAA/fulltext"
- );
- $this->assert403($response);
- }
-
- public function testSetItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $attachmentKey = API::createAttachmentItem("imported_url", [], $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey/fulltext"
- );
- $this->assert404($response);
- $this->assertNull($response->getHeader("Last-Modified-Version"));
-
- $libraryVersion = API::getLibraryVersion();
-
- $content = "Here is some full-text content";
- $pages = 50;
-
- // No Content-Type
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- $content
- );
- $this->assert400($response, "Content-Type must be application/json");
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages,
- "invalidParam" => "shouldBeIgnored"
- ]),
- array("Content-Type: application/json")
- );
-
- $this->assert204($response);
- $contentVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($libraryVersion, $contentVersion);
-
- // Retrieve it
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey/fulltext"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($content, $json['content']);
- $this->assertArrayHasKey('indexedPages', $json);
- $this->assertArrayHasKey('totalPages', $json);
- $this->assertEquals($pages, $json['indexedPages']);
- $this->assertEquals($pages, $json['totalPages']);
- $this->assertArrayNotHasKey("indexedChars", $json);
- $this->assertArrayNotHasKey("invalidParam", $json);
- $this->assertEquals($contentVersion, $response->getHeader("Last-Modified-Version"));
- }
-
-
- public function testSetItemContentMultiple() {
- $key = API::createItem("book", false, $this, 'key');
- $attachmentKey1 = API::createAttachmentItem("imported_url", [], $key, $this, 'key');
- $attachmentKey2 = API::createAttachmentItem("imported_url", [], $key, $this, 'key');
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = [
- [
- "key" => $attachmentKey1,
- "content" => "Here is some full-text content",
- "indexedPages" => 50,
- "totalPages" => 50,
- "invalidParam" => "shouldBeIgnored"
- ],
- [
- "content" => "This is missing a key and should be skipped",
- "indexedPages" => 20,
- "totalPages" => 40
- ],
- [
- "key" => $attachmentKey2,
- "content" => "Here is some more full-text content",
- "indexedPages" => 20,
- "totalPages" => 40
- ]
- ];
-
- // No Content-Type
- $response = API::userPost(
- self::$config['userID'],
- "fulltext",
- json_encode($json),
- [
- "If-Unmodified-Since-Version: $libraryVersion"
- ]
- );
- $this->assert400($response, "Content-Type must be application/json");
-
- // No If-Unmodified-Since-Version
- $response = API::userPost(
- self::$config['userID'],
- "fulltext",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert428($response, "If-Unmodified-Since-Version not provided");
-
- // Store content
- $response = API::userPost(
- self::$config['userID'],
- "fulltext",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $libraryVersion"
- ]
- );
-
- $this->assert200($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert400ForObject($response, false, 1);
- $this->assert200ForObject($response, false, 2);
- $newLibraryVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($libraryVersion, $newLibraryVersion);
- $libraryVersion = $newLibraryVersion;
-
- $originalJSON = $json;
-
- // Retrieve content
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey1/fulltext"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($originalJSON[0]['content'], $json['content']);
- $this->assertEquals($originalJSON[0]['indexedPages'], $json['indexedPages']);
- $this->assertEquals($originalJSON[0]['totalPages'], $json['totalPages']);
- $this->assertArrayNotHasKey("indexedChars", $json);
- $this->assertArrayNotHasKey("invalidParam", $json);
- $this->assertEquals($libraryVersion, $response->getHeader("Last-Modified-Version"));
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey2/fulltext"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($originalJSON[2]['content'], $json['content']);
- $this->assertEquals($originalJSON[2]['indexedPages'], $json['indexedPages']);
- $this->assertEquals($originalJSON[2]['totalPages'], $json['totalPages']);
- $this->assertArrayNotHasKey("indexedChars", $json);
- $this->assertArrayNotHasKey("invalidParam", $json);
- $this->assertEquals($libraryVersion, $response->getHeader("Last-Modified-Version"));
- }
-
-
- public function testModifyAttachmentWithFulltext() {
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $attachmentKey = $json['key'];
- $content = "Here is some full-text content";
- $pages = 50;
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- $json['title'] = "This is a new attachment title";
- $json['contentType'] = 'text/plain';
-
- // Modify attachment item
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey",
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $json['version'])
- );
- $this->assert204($response);
- }
-
-
- public function testSinceContent() {
- self::_testSinceContent('since');
- self::_testSinceContent('newer');
- }
-
-
- public function testSearchItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $attachmentKey = $json['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey/fulltext"
- );
- $this->assert404($response);
-
- $content = "Here is some unique full-text content";
- $pages = 50;
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- json_encode([
- "content" => $content,
- "indexedPages" => $pages,
- "totalPages" => $pages
- ]),
- array("Content-Type: application/json")
- );
-
- $this->assert204($response);
-
- // Wait for indexing via Lambda
- sleep(3);
-
- // Search for a word
- $response = API::userGet(
- self::$config['userID'],
- "items?q=unique&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($json['key'], trim($response->getBody()));
-
- // Search for a phrase
- $response = API::userGet(
- self::$config['userID'],
- "items?q=unique%20full-text&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($attachmentKey, trim($response->getBody()));
-
- // Search for nonexistent word
- $response = API::userGet(
- self::$config['userID'],
- "items?q=nothing&qmode=everything&format=keys"
- . "&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals("", trim($response->getBody()));
- }
-
-
- public function testDeleteItemContent() {
- $key = API::createItem("book", false, $this, 'key');
- $attachmentKey = API::createAttachmentItem("imported_file", [], $key, $this, 'key');
-
- $content = "Ыюм мютат дэбетиз конвынёры эю, ку мэль жкрипта трактатоз.\nПро ут чтэт эрепюят граэкйж, дуо нэ выро рыкючабо пырикюлёз.";
-
- // Store content
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- json_encode([
- "content" => $content,
- "indexedPages" => 50
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion = $response->getHeader("Last-Modified-Version");
-
- // Retrieve it
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey/fulltext"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals($content, $json['content']);
- $this->assertEquals(50, $json['indexedPages']);
-
- // Set to empty string
- $response = API::userPut(
- self::$config['userID'],
- "items/$attachmentKey/fulltext",
- json_encode([
- "content" => ""
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $this->assertGreaterThan($contentVersion, $response->getHeader("Last-Modified-Version"));
-
- // Make sure it's gone
- $response = API::userGet(
- self::$config['userID'],
- "items/$attachmentKey/fulltext"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertEquals("", $json['content']);
- $this->assertArrayNotHasKey("indexedPages", $json);
- }
-
-
- private function _testSinceContent($param) {
- API::userClear(self::$config['userID']);
-
- // Store content for one item
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $key1 = $json['key'];
-
- $content = "Here is some full-text content";
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key1/fulltext",
- json_encode([
- "content" => $content
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion1 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan(0, $contentVersion1);
-
- // And another
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $key2 = $json['key'];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key2/fulltext",
- json_encode([
- "content" => $content
- ]),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $contentVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan(0, $contentVersion2);
-
- // Get newer one
- $response = API::userGet(
- self::$config['userID'],
- "fulltext?$param=$contentVersion1"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $this->assertEquals($contentVersion2, $response->getHeader("Last-Modified-Version"));
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $this->assertArrayHasKey($key2, $json);
- $this->assertEquals($contentVersion2, $json[$key2]);
-
- // Get both with since=0
- $response = API::userGet(
- self::$config['userID'],
- "fulltext?$param=0"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json);
- $this->assertArrayHasKey($key1, $json);
- $this->assertEquals($contentVersion1, $json[$key1]);
- $this->assertArrayHasKey($key1, $json);
- $this->assertEquals($contentVersion2, $json[$key2]);
- }
-}
diff --git a/tests/remote/tests/API/3/GeneralTest.php b/tests/remote/tests/API/3/GeneralTest.php
deleted file mode 100644
index 27376623..00000000
--- a/tests/remote/tests/API/3/GeneralTest.php
+++ /dev/null
@@ -1,93 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class GeneralTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testZoteroWriteToken() {
- $json = API::getItemTemplate("book");
-
- $token = md5(uniqid());
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array(
- "Content-Type: application/json",
- "Zotero-Write-Token: $token"
- )
- );
- $this->assert200ForObject($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array(
- "Content-Type: application/json",
- "Zotero-Write-Token: $token"
- )
- );
- $this->assert412($response);
- }
-
-
- public function testInvalidCharacters() {
- $data = array(
- 'title' => "A" . chr(0) . "A",
- 'creators' => array(
- array(
- 'creatorType' => "author",
- 'name' => "B" . chr(1) . "B"
- )
- ),
- 'tags' => array(
- array(
- 'tag' => "C" . chr(2) . "C"
- )
- )
- );
- $json = API::createItem("book", $data, $this, 'jsonData');
- $this->assertEquals("AA", $json['title']);
- $this->assertEquals("BB", $json['creators'][0]['name']);
- $this->assertEquals("CC", $json['tags'][0]['tag']);
- }
-}
diff --git a/tests/remote/tests/API/3/GroupTest.php b/tests/remote/tests/API/3/GroupTest.php
deleted file mode 100644
index 50963d7f..00000000
--- a/tests/remote/tests/API/3/GroupTest.php
+++ /dev/null
@@ -1,277 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API, SimpleXMLElement;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class GroupTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
- /**
- * Changing a group's metadata should change its version
- */
- public function testUpdateMetadataJSON() {
- $response = API::userGet(
- self::$config['userID'],
- "groups"
- );
- $this->assert200($response);
-
- // Get group API URI and version
- $json = API::getJSONFromResponse($response)[0];
- $groupID = $json['id'];
- $url = $json['links']['self']['href'];
- $url = str_replace(self::$config['apiURLPrefix'], '', $url);
- $version = $json['version'];
-
- // Make sure format=versions returns the same version
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($version, json_decode($response->getBody())->$groupID);
-
- // Update group metadata
- $xml = new SimpleXMLElement(" ");
- foreach ($json['data'] as $key => $val) {
- switch ($key) {
- case 'id':
- case 'version':
- case 'members':
- continue;
-
- case 'name':
- $name = "My Test Group " . uniqid();
- $xml['name'] = $name;
- break;
-
- case 'description':
- $description = "This is a test description " . uniqid();
- $xml->$key = $description;
- break;
-
- case 'url':
- $urlField = "http://example.com/" . uniqid();
- $xml->$key = $urlField;
- break;
-
- default:
- $xml[$key] = $val;
- }
- }
- $xml = trim(preg_replace('/^<\?xml.+\n/', "", $xml->asXML()));
-
- $response = API::put(
- $url,
- $xml,
- array("Content-Type: text/xml"),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $xml->registerXPathNamespace('zxfer', 'http://zotero.org/ns/transfer');
- $group = $xml->xpath('//atom:entry/atom:content/zxfer:group');
- $this->assertCount(1, $group);
- $this->assertEquals($name, $group[0]['name']);
-
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $newVersion = $json->$groupID;
- $this->assertNotEquals($version, $newVersion);
-
- // Check version header on individual group request
- $response = API::groupGet(
- $groupID,
- ""
- );
- $this->assert200($response);
- $this->assertEquals($newVersion, $response->getHeader('Last-Modified-Version'));
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($name, $json['name']);
- $this->assertEquals($description, $json['description']);
- $this->assertEquals($urlField, $json['url']);
- }
-
-
- /**
- * Changing a group's metadata should change its version
- */
- public function testUpdateMetadataAtom() {
- $response = API::userGet(
- self::$config['userID'],
- "groups?content=json&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
-
- // Get group API URI and version
- $xml = API::getXMLFromResponse($response);
- $xml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
- $xml->registerXPathNamespace('zapi', 'http://zotero.org/ns/api');
- $groupID = (string) array_get_first($xml->xpath("//atom:entry/zapi:groupID"));
- $url = (string) array_get_first($xml->xpath("//atom:entry/atom:link[@rel='self']/@href"));
- $url = str_replace(self::$config['apiURLPrefix'], '', $url);
- $version = json_decode(API::parseDataFromAtomEntry($xml)['content'], true)['version'];
-
- // Make sure format=versions returns the same version
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertEquals($version, $json->$groupID);
-
- // Update group metadata
- $json = json_decode(array_get_first($xml->xpath("//atom:entry/atom:content")));
- $xml = new SimpleXMLElement(" ");
- foreach ($json as $key => $val) {
- switch ($key) {
- case 'id':
- case 'members':
- continue;
-
- case 'name':
- $name = "My Test Group " . uniqid();
- $xml['name'] = $name;
- break;
-
- case 'description':
- $description = "This is a test description " . uniqid();
- $xml->$key = $description;
- break;
-
- case 'url':
- $urlField = "http://example.com/" . uniqid();
- $xml->$key = $urlField;
- break;
-
- default:
- $xml[$key] = $val;
- }
- }
- $xml = trim(preg_replace('/^<\?xml.+\n/', "", $xml->asXML()));
-
- $response = API::put(
- $url,
- $xml,
- array("Content-Type: text/xml"),
- array(
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- )
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $xml->registerXPathNamespace('zxfer', 'http://zotero.org/ns/transfer');
- $group = $xml->xpath('//atom:entry/atom:content/zxfer:group');
- $this->assertCount(1, $group);
- $this->assertEquals($name, $group[0]['name']);
-
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $newVersion = $json->$groupID;
- $this->assertNotEquals($version, $newVersion);
-
- // Check version header on individual group request
- $response = API::groupGet(
- $groupID,
- "?content=json&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $this->assertEquals($newVersion, $response->getHeader('Last-Modified-Version'));
- $json = json_decode(API::getContentFromResponse($response));
- $this->assertEquals($name, $json->name);
- $this->assertEquals($description, $json->description);
- $this->assertEquals($urlField, $json->url);
- }
-
-
- public function testUpdateMemberJSON() {
- $groupID = API::createGroup([
- 'owner' => self::$config['userID'],
- 'type' => 'Private',
- 'libraryReading' => 'all'
- ]);
-
- // Get group version
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $version = json_decode($response->getBody())->$groupID;
-
- $response = API::superPost(
- "groups/$groupID/users",
- ' ',
- ["Content-Type: text/xml"]
- );
- $this->assert200($response);
-
- // Group metadata version should have changed
- $response = API::userGet(
- self::$config['userID'],
- "groups?format=versions&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $newVersion = $json->$groupID;
- $this->assertNotEquals($version, $newVersion);
-
- // Check version header on individual group request
- $response = API::groupGet($groupID, "");
- $this->assert200($response);
- $this->assertEquals($newVersion, $response->getHeader('Last-Modified-Version'));
-
- API::deleteGroup($groupID);
- }
-}
-?>
diff --git a/tests/remote/tests/API/3/ItemTest.php b/tests/remote/tests/API/3/ItemTest.php
deleted file mode 100644
index f86377ab..00000000
--- a/tests/remote/tests/API/3/ItemTest.php
+++ /dev/null
@@ -1,2539 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class ItemTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function testNewEmptyBookItem() {
- $json = API::createItem("book", false, $this, 'jsonData');
- $this->assertEquals("book", (string) $json['itemType']);
- $this->assertTrue("" === $json['title']);
- return $json;
- }
-
-
- public function testNewEmptyBookItemMultiple() {
- $json = API::getItemTemplate("book");
-
- $data = array();
- $json->title = "A";
- $data[] = $json;
- $json2 = clone $json;
- $json2->title = "B";
- $data[] = $json2;
- $json3 = clone $json;
- $json3->title = "C";
- $json3->numPages = 200;
- $data[] = $json3;
-
- $response = API::postItems($data);
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(3, $json['successful']);
- // Deprecated
- $this->assertCount(3, $json['success']);
-
- // Check data in write response
- for ($i = 0; $i < 3; $i++) {
- $this->assertEquals($json['successful'][$i]['key'], $json['successful'][$i]['data']['key']);
- $this->assertEquals($libraryVersion, $json['successful'][$i]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][$i]['data']['version']);
- $this->assertEquals($data[$i]->title, $json['successful'][$i]['data']['title']);
- }
- //$this->assertArrayNotHasKey('numPages', $json['successful'][0]['data']);
- //$this->assertArrayNotHasKey('numPages', $json['successful'][1]['data']);
- $this->assertEquals($data[2]->numPages, $json['successful'][2]['data']['numPages']);
-
- // Check in separate request, to be safe
- $json = API::getItem($json['success'], $this, 'json');
- $itemJSON = array_shift($json);
- $this->assertEquals("A", $itemJSON['data']['title']);
- $itemJSON = array_shift($json);
- $this->assertEquals("B", $itemJSON['data']['title']);
- $itemJSON = array_shift($json);
- $this->assertEquals("C", $itemJSON['data']['title']);
- $this->assertEquals(200, $itemJSON['data']['numPages']);
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testEditBookItem($json) {
- $key = $json['key'];
- $version = $json['version'];
-
- $newTitle = "New Title";
- $numPages = 100;
- $creatorType = "author";
- $firstName = "Firstname";
- $lastName = "Lastname";
-
- $json['title'] = $newTitle;
- $json['numPages'] = $numPages;
- $json['creators'][] = array(
- 'creatorType' => $creatorType,
- 'firstName' => $firstName,
- 'lastName' => $lastName
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json')['data'];
-
- $this->assertEquals($newTitle, $json['title']);
- $this->assertEquals($numPages, $json['numPages']);
- $this->assertEquals($creatorType, $json['creators'][0]['creatorType']);
- $this->assertEquals($firstName, $json['creators'][0]['firstName']);
- $this->assertEquals($lastName, $json['creators'][0]['lastName']);
- }
-
-
- public function testDate() {
- $date = 'Sept 18, 2012';
- $parsedDate = '2012-09-18';
-
- $json = API::createItem("book", array(
- "date" => $date
- ), $this, 'jsonData');
- $key = $json['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($date, $json['data']['date']);
-
- // meta.parsedDate (JSON)
- $this->assertEquals($parsedDate, $json['meta']['parsedDate']);
-
- // zapi:parsedDate (Atom)
- $xml = API::getItem($key, $this, 'atom');
- $this->assertEquals($parsedDate, array_get_first($xml->xpath('/atom:entry/zapi:parsedDate')));
- }
-
-
- public function testDateWithoutDay() {
- $date = 'Sept 2012';
- $parsedDate = '2012-09';
-
- $json = API::createItem("book", array(
- "date" => $date
- ), $this, 'jsonData');
- $key = $json['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($date, $json['data']['date']);
-
- // meta.parsedDate (JSON)
- $this->assertEquals($parsedDate, $json['meta']['parsedDate']);
-
- // zapi:parsedDate (Atom)
- $xml = API::getItem($key, $this, 'atom');
- $this->assertEquals($parsedDate, array_get_first($xml->xpath('/atom:entry/zapi:parsedDate')));
- }
-
-
- public function testDateWithoutMonth() {
- $date = '2012';
- $parsedDate = '2012';
-
- $json = API::createItem("book", array(
- "date" => $date
- ), $this, 'jsonData');
- $key = $json['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($date, $json['data']['date']);
-
- // meta.parsedDate (JSON)
- $this->assertEquals($parsedDate, $json['meta']['parsedDate']);
-
- // zapi:parsedDate (Atom)
- $xml = API::getItem($key, $this, 'atom');
- $this->assertEquals($parsedDate, array_get_first($xml->xpath('/atom:entry/zapi:parsedDate')));
- }
-
-
- public function testDateUnparseable() {
- $json = API::createItem("book", array(
- "date" => 'n.d.'
- ), $this, 'jsonData');
- $key = $json['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals('n.d.', $json['data']['date']);
-
- // meta.parsedDate (JSON)
- $this->assertArrayNotHasKey('parsedDate', $json['meta']);
-
- // zapi:parsedDate (Atom)
- $xml = API::getItem($key, $this, 'atom');
- $this->assertCount(0, $xml->xpath('/atom:entry/zapi:parsedDate'));
- }
-
-
- public function testDateAccessed8601() {
- $date = '2014-02-01T01:23:45Z';
- $data = API::createItem("book", array(
- 'accessDate' => $date
- ), $this, 'jsonData');
- $this->assertEquals($date, $data['accessDate']);
- }
-
-
- public function testDateAccessed8601TZ() {
- $date = '2014-02-01T01:23:45-0400';
- $dateUTC = '2014-02-01T05:23:45Z';
- $data = API::createItem("book", array(
- 'accessDate' => $date
- ), $this, 'jsonData');
- $this->assertEquals($dateUTC, $data['accessDate']);
- }
-
-
- public function testDateAccessedSQL() {
- $date = '2014-02-01 01:23:45';
- $date8601 = '2014-02-01T01:23:45Z';
- $data = API::createItem("book", array(
- 'accessDate' => $date
- ), $this, 'jsonData');
- $this->assertEquals($date8601, $data['accessDate']);
- }
-
-
- public function testDateAccessedInvalid() {
- $date = 'February 1, 2014';
- $response = API::createItem("book", array(
- 'accessDate' => $date
- ), $this, 'response');
- $this->assert400ForObject($response, "'accessDate' must be in ISO 8601 or UTC 'YYYY-MM-DD[ hh-mm-dd]' format or 'CURRENT_TIMESTAMP' (February 1, 2014)");
- }
-
-
- public function testDateAddedNewItem8601() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $dateAdded = "2013-03-03T21:33:53Z";
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test",
- "dateAdded" => $dateAdded
- );
- $data = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $this->assertEquals($dateAdded, $data['dateAdded']);
- }
-
-
- public function testDateAddedNewItem8601TZ() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $dateAdded = "2013-03-03T17:33:53-0400";
- $dateAddedUTC = "2013-03-03T21:33:53Z";
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test",
- "dateAdded" => $dateAdded
- );
- $data = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $this->assertEquals($dateAddedUTC, $data['dateAdded']);
- }
-
-
- public function testDateAddedNewItemSQL() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $dateAdded = "2013-03-03 21:33:53";
- $dateAdded8601 = "2013-03-03T21:33:53Z";
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test",
- "dateAdded" => $dateAdded
- );
- $data = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $this->assertEquals($dateAdded8601, $data['dateAdded']);
- }
-
-
- public function testDateAddedExistingItem() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'item':
- $itemData = [
- "title" => "Test",
- "dateAdded" => "2017-03-12T02:48:54Z"
- ];
- $data = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $objectKey = $data['key'];
- $originalDateAdded = $data['dateAdded'];
-
- // If date added hasn't changed, allow
- $data['title'] = "Test 2";
- $data['dateAdded'] = $originalDateAdded;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($data)
- );
- $this->assert204($response);
- $data = API::getItem($objectKey, $this, 'json')['data'];
-
- // And even if it's a different timezone
- $date = \DateTime::createFromFormat(\DateTime::ISO8601, $originalDateAdded);
- $date->setTimezone(new \DateTimeZone('America/New_York'));
- $newDateAdded = $date->format('c');
-
- $data['title'] = "Test 3";
- $data['dateAdded'] = $newDateAdded;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($data)
- );
- $this->assert204($response);
- $data = API::getItem($objectKey, $this, 'json')['data'];
-
- // But with a changed dateAdded, disallow
- $newDateAdded = "2017-04-01T00:00:00Z";
- $data['title'] = "Test 4";
- $data['dateAdded'] = $newDateAdded;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($data)
- );
- $this->assert400($response, "'dateAdded' cannot be modified for existing $objectTypePlural");
-
- // Unless it's exactly one hour off, because there's a DST bug we haven't fixed
- // https://github.com/zotero/zotero/issues/1201
- $newDateAdded = "2017-03-12T01:48:54Z";
- $data['dateAdded'] = $newDateAdded;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($data)
- );
- $this->assert204($response);
- $data = API::getItem($objectKey, $this, 'json')['data'];
- // But the value shouldn't have actually changed
- $this->assertEquals($originalDateAdded, $data['dateAdded']);
- }
-
-
- public function testDateModified() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test"
- );
- $json = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $objectKey = $json['key'];
- $dateModified1 = $json['dateModified'];
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If no explicit dateModified, use current timestamp
- //
- $json['title'] = "Test 2";
- unset($json['dateModified']);
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
-
- $dateModified2 = $json['dateModified'];
- $this->assertNotEquals($dateModified1, $dateModified2);
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If existing dateModified, use current timestamp
- //
- $json['title'] = "Test 3";
- $json['dateModified'] = trim(preg_replace("/[TZ]/", " ", $dateModified2));
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
-
- $dateModified3 = $json['dateModified'];
- $this->assertNotEquals($dateModified2, $dateModified3);
-
- //
- // If explicit dateModified, use that
- //
- $newDateModified = "2013-03-03T21:33:53Z";
- $json['title'] = "Test 4";
- $json['dateModified'] = $newDateModified;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json)
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
- $dateModified4 = $json['dateModified'];
- $this->assertEquals($newDateModified, $dateModified4);
- }
-
-
- // TODO: Make this the default and remove above after clients update code
- public function testDateModifiedTmpZoteroClientHack() {
- // In case this is ever extended to other objects
- $objectType = 'item';
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'item':
- $itemData = array(
- "title" => "Test"
- );
- $json = API::createItem("videoRecording", $itemData, $this, 'jsonData');
- break;
- }
-
- $objectKey = $json['key'];
- $dateModified1 = $json['dateModified'];
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If no explicit dateModified, use current timestamp
- //
- $json['title'] = "Test 2";
- unset($json['dateModified']);
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- // TODO: Remove
- [
- "User-Agent: Firefox"
- ]
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
-
- $dateModified2 = $json['dateModified'];
- $this->assertNotEquals($dateModified1, $dateModified2);
-
- // Make sure we're in the next second
- sleep(1);
-
- //
- // If dateModified provided and hasn't changed, use that
- //
- $json['title'] = "Test 3";
- $json['dateModified'] = trim(preg_replace("/[TZ]/", " ", $dateModified2));
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- // TODO: Remove
- [
- "User-Agent: Firefox"
- ]
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
-
- $this->assertEquals($dateModified2, $json['dateModified']);
-
- //
- // If dateModified is provided and has changed, use that
- //
- $newDateModified = "2013-03-03T21:33:53Z";
- $json['title'] = "Test 4";
- $json['dateModified'] = $newDateModified;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- // TODO: Remove
- [
- "User-Agent: Firefox"
- ]
- );
- $this->assert204($response);
-
- switch ($objectType) {
- case 'item':
- $json = API::getItem($objectKey, $this, 'json')['data'];
- break;
- }
- $this->assertEquals($newDateModified, $json['dateModified']);
- }
-
-
- public function testDateModifiedCollectionChange() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
- $json = API::createItem("book", ["title" => "Test"], $this, 'jsonData');
-
- $objectKey = $json['key'];
- $dateModified1 = $json['dateModified'];
-
- $json['collections'] = [$collectionKey];
-
- // Make sure we're in the next second
- sleep(1);
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert200ForObject($response);
-
- $json = API::getItem($objectKey, $this, 'json')['data'];
- $dateModified2 = $json['dateModified'];
-
- // Date Modified shouldn't have changed
- $this->assertEquals($dateModified1, $dateModified2);
- }
-
-
- public function testChangeItemType() {
- $json = API::getItemTemplate("book");
- $json->title = "Foo";
- $json->numPages = 100;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $key = API::getFirstSuccessKeyFromResponse($response);
- $json1 = API::getItem($key, $this, 'json')['data'];
- $version = $json1['version'];
-
- $json2 = API::getItemTemplate("bookSection");
-
- foreach ($json2 as $field => &$val) {
- if ($field != "itemType" && isset($json1[$field])) {
- $val = $json1[$field];
- }
- }
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json2),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json')['data'];
- $this->assertEquals("bookSection", $json['itemType']);
- $this->assertEquals("Foo", $json['title']);
- $this->assertArrayNotHasKey("numPages", $json);
- }
-
-
- //
- // PATCH (single item)
- //
- public function testPatchItem() {
- $itemData = array(
- "title" => "Test"
- );
- $json = API::createItem("book", $itemData, $this, 'jsonData');
- $itemKey = $json['key'];
- $itemVersion = $json['version'];
-
- $patch = function ($context, $config, $itemKey, $itemVersion, &$itemData, $newData) {
- foreach ($newData as $field => $val) {
- $itemData[$field] = $val;
- }
- $response = API::userPatch(
- $config['userID'],
- "items/$itemKey?key=" . $config['apiKey'],
- json_encode($newData),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion"
- )
- );
- $context->assert204($response);
- $json = API::getItem($itemKey, $this, 'json')['data'];
-
- foreach ($itemData as $field => $val) {
- $context->assertEquals($val, $json[$field]);
- }
- $headerVersion = $response->getHeader("Last-Modified-Version");
- $context->assertGreaterThan($itemVersion, $headerVersion);
- $context->assertEquals($json['version'], $headerVersion);
-
- return $headerVersion;
- };
-
- $newData = array(
- "date" => "2013"
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = array(
- "title" => ""
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = array(
- "tags" => array(
- array(
- "tag" => "Foo"
- )
- )
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = array(
- "tags" => array()
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $key = API::createCollection('Test', false, $this, 'key');
- $newData = array(
- "collections" => array($key)
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = array(
- "collections" => array()
- );
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
- }
-
- public function testPatchAttachment() {
- $json = API::createAttachmentItem("imported_file", [], false, $this, 'jsonData');
- $itemKey = $json['key'];
- $itemVersion = $json['version'];
-
- $filename = "test.pdf";
- $mtime = 1234567890000;
- $md5 = "390d914fdac33e307e5b0e1f3dba9da2";
-
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey",
- json_encode([
- "filename" => $filename,
- "mtime" => $mtime,
- "md5" => $md5,
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion"
- ]
- );
- $this->assert204($response);
- $json = API::getItem($itemKey, $this, 'json')['data'];
-
- $this->assertEquals($filename, $json['filename']);
- $this->assertEquals($mtime, $json['mtime']);
- $this->assertEquals($md5, $json['md5']);
- $headerVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($itemVersion, $headerVersion);
- $this->assertEquals($json['version'], $headerVersion);
- }
-
- public function testPatchNote() {
- $text = "Test
";
- $newText = "Test 2
";
- $json = API::createNoteItem($text, false, $this, 'jsonData');
- $itemKey = $json['key'];
- $itemVersion = $json['version'];
-
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey",
- json_encode([
- "note" => $newText
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion"
- ]
- );
- $this->assert204($response);
- $json = API::getItem($itemKey, $this, 'json')['data'];
-
- $this->assertEquals($newText, $json['note']);
- $headerVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($itemVersion, $headerVersion);
- $this->assertEquals($json['version'], $headerVersion);
- }
-
- public function testPatchNoteOnBookError() {
- $json = API::createItem("book", [], $this, 'jsonData');
- $itemKey = $json['key'];
- $itemVersion = $json['version'];
-
- $response = API::userPatch(
- self::$config['userID'],
- "items/$itemKey",
- json_encode([
- "note" => "Test"
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $itemVersion"
- ]
- );
- $this->assert400($response, "'note' property is valid only for note and attachment items");
- }
-
- //
- // PATCH (multiple items)
- //
- public function testPatchItems() {
- $itemData = [
- "title" => "Test"
- ];
- $json = API::createItem("book", $itemData, $this, 'jsonData');
- $itemKey = $json['key'];
- $itemVersion = $json['version'];
-
- $patch = function ($context, $config, $itemKey, $itemVersion, &$itemData, $newData) {
- foreach ($newData as $field => $val) {
- $itemData[$field] = $val;
- }
- $newData['key'] = $itemKey;
- $newData['version'] = $itemVersion;
- $response = API::userPost(
- $config['userID'],
- "items",
- json_encode([$newData]),
- [
- "Content-Type: application/json"
- ]
- );
- $context->assert200ForObject($response);
- $json = API::getItem($itemKey, $this, 'json')['data'];
-
- foreach ($itemData as $field => $val) {
- $context->assertEquals($val, $json[$field]);
- }
- $headerVersion = $response->getHeader("Last-Modified-Version");
- $context->assertGreaterThan($itemVersion, $headerVersion);
- $context->assertEquals($json['version'], $headerVersion);
-
- return $headerVersion;
- };
-
- $newData = [
- "date" => "2013"
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = [
- "title" => ""
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = [
- "tags" => [
- [
- "tag" => "Foo"
- ]
- ]
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = [
- "tags" => []
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $key = API::createCollection('Test', false, $this, 'key');
- $newData = [
- "collections" => [$key]
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
-
- $newData = [
- "collections" => []
- ];
- $itemVersion = $patch($this, self::$config, $itemKey, $itemVersion, $itemData, $newData);
- }
-
- public function testNewComputerProgramItem() {
- $data = API::createItem("computerProgram", false, $this, 'jsonData');
- $key = $data['key'];
- $this->assertEquals("computerProgram", $data['itemType']);
-
- $version = "1.0";
- $data['versionNumber'] = $version;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($data),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json');
- $this->assertEquals($version, $json['data']['versionNumber']);
- }
-
-
- public function testNewInvalidBookItem() {
- $json = API::getItemTemplate("book");
-
- // Missing item type
- $json2 = clone $json;
- unset($json2->itemType);
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json2]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'itemType' property not provided");
-
- // contentType on non-attachment
- $json2 = clone $json;
- $json2->contentType = "text/html";
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json2]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'contentType' is valid only for attachment items");
-
- // more tests
- }
-
-
- public function testEditTopLevelNote() {
- $noteText = "Test
";
-
- $json = API::createNoteItem($noteText, null, $this, 'jsonData');
- $noteText = "Test Test
";
- $json['note'] = $noteText;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json)
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($noteText, $json['note']);
- }
-
-
- public function testEditChildNote() {
- $noteText = "Test
";
- $key = API::createItem("book", [ "title" => "Test" ], $this, 'key');
- $json = API::createNoteItem($noteText, $key, $this, 'jsonData');
- $noteText = "Test Test
";
- $json['note'] = $noteText;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json)
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "items/{$json['key']}"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response)['data'];
- $this->assertEquals($noteText, $json['note']);
- }
-
-
- public function testConvertChildNoteToParentViaPatch() {
- $key = API::createItem("book", [ "title" => "Test" ], $this, 'key');
- $json = API::createNoteItem("", $key, $this, 'jsonData');
- $json['parentItem'] = false;
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json)
- );
- $this->assert204($response);
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertArrayNotHasKey('parentItem', $json);
- }
-
-
- public function test_should_convert_child_note_to_top_level_and_add_to_collection_via_PATCH() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
- $parentItemKey = API::createItem("book", false, $this, 'key');
- $noteJSON = API::createNoteItem("", $parentItemKey, $this, 'jsonData');
- $noteJSON['parentItem'] = false;
- $noteJSON['collections'] = [$collectionKey];
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$noteJSON['key']}",
- json_encode($noteJSON)
- );
- $this->assert204($response);
- $json = API::getItem($noteJSON['key'], $this, 'json')['data'];
- $this->assertArrayNotHasKey('parentItem', $json);
- $this->assertCount(1, $json['collections']);
- $this->assertEquals($collectionKey, $json['collections'][0]);
- }
-
-
- public function test_should_convert_child_note_to_top_level_and_add_to_collection_via_PUT() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
- $parentItemKey = API::createItem("book", false, $this, 'key');
- $noteJSON = API::createNoteItem("", $parentItemKey, $this, 'jsonData');
- unset($noteJSON['parentItem']);
- $noteJSON['collections'] = [$collectionKey];
- $response = API::userPut(
- self::$config['userID'],
- "items/{$noteJSON['key']}",
- json_encode($noteJSON)
- );
- $this->assert204($response);
- $json = API::getItem($noteJSON['key'], $this, 'json')['data'];
- $this->assertArrayNotHasKey('parentItem', $json);
- $this->assertCount(1, $json['collections']);
- $this->assertEquals($collectionKey, $json['collections'][0]);
- }
-
-
- public function testEditTitleWithCollectionInMultipleMode() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $json = API::createItem("book", [
- "title" => "A",
- "collections" => [
- $collectionKey
- ]
- ], $this, 'jsonData');
-
- $version = $json['version'];
- $json['title'] = "B";
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert200ForObject($response);
-
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertEquals("B", $json['title']);
- $this->assertGreaterThan($version, $json['version']);
- }
-
-
- public function testEditTitleWithTagInMultipleMode() {
- $tag1 = [
- "tag" => "foo",
- "type" => 1
- ];
- $tag2 = [
- "tag" => "bar"
- ];
-
- $json = API::createItem("book", [
- "title" => "A",
- "tags" => [$tag1]
- ], $this, 'jsonData');
-
- $this->assertCount(1, $json['tags']);
- $this->assertEquals($tag1, $json['tags'][0]);
-
- $version = $json['version'];
- $json['title'] = "B";
- $json['tags'][] = $tag2;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert200ForObject($response);
-
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertEquals("B", $json['title']);
- $this->assertGreaterThan($version, $json['version']);
- $this->assertCount(2, $json['tags']);
- $this->assertContains($tag1, $json['tags']);
- $this->assertContains($tag2, $json['tags']);
- }
-
-
- public function testNewEmptyAttachmentFields() {
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
- $this->assertNull($json['md5']);
- $this->assertNull($json['mtime']);
- }
-
-
- public function testNewTopLevelImportedFileAttachment() {
- $response = API::get("items/new?itemType=attachment&linkMode=imported_file");
- $json = json_decode($response->getBody());
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- }
-
-
- public function testNewItemTemplateAttachmentFields() {
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $this->assertSame('', $json->url);
- $this->assertObjectNotHasAttribute('filename', $json);
- $this->assertObjectNotHasAttribute('path', $json);
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_file");
- $json = json_decode($response->getBody());
- $this->assertSame('', $json->path);
- $this->assertObjectNotHasAttribute('filename', $json);
- $this->assertObjectNotHasAttribute('url', $json);
-
- $response = API::get("items/new?itemType=attachment&linkMode=imported_url");
- $json = json_decode($response->getBody());
- $this->assertSame('', $json->filename);
- $this->assertSame('', $json->url);
- $this->assertObjectNotHasAttribute('path', $json);
-
- $response = API::get("items/new?itemType=attachment&linkMode=imported_file");
- $json = json_decode($response->getBody());
- $this->assertSame('', $json->filename);
- $this->assertObjectNotHasAttribute('path', $json);
- $this->assertObjectNotHasAttribute('url', $json);
- }
-
-
- /*
- Disabled -- see note at Zotero_Item::checkTopLevelAttachment()
-
- public function testNewInvalidTopLevelAttachment() {
- $linkModes = array("linked_url", "imported_url");
- foreach ($linkModes as $linkMode) {
- $response = API::get("items/new?itemType=attachment&linkMode=$linkMode");
- $json = json_decode($response->getBody());
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "Only file attachments and PDFs can be top-level items");
- }
- }
- */
-
-
- /**
- * It should be possible to edit an existing PDF attachment without sending 'contentType'
- * (which would cause a new attachment to be rejected)
- */
- /*
- Disabled -- see note at Zotero_Item::checkTopLevelAttachment()
-
- public function testPatchTopLevelAttachment() {
- $json = API::createAttachmentItem("imported_url", [
- 'title' => 'A',
- 'contentType' => 'application/pdf',
- 'filename' => 'test.pdf'
- ], false, $this, 'jsonData');
-
- // With 'attachment' and 'linkMode'
- $json = [
- 'itemType' => 'attachment',
- 'linkMode' => 'imported_url',
- 'key' => $json['key'],
- 'version' => $json['version'],
- 'title' => 'B'
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert200ForObject($response);
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertEquals("B", $json['title']);
-
- // Without 'linkMode'
- $json = [
- 'itemType' => 'attachment',
- 'key' => $json['key'],
- 'version' => $json['version'],
- 'title' => 'C'
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert200ForObject($response);
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertEquals("C", $json['title']);
-
- // Without 'itemType' or 'linkMode'
- $json = [
- 'key' => $json['key'],
- 'version' => $json['version'],
- 'title' => 'D'
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert200ForObject($response);
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertEquals("D", $json['title']);
- }*/
-
-
- public function testNewEmptyLinkAttachmentItemWithItemKey() {
- $key = API::createItem("book", false, $this, 'key');
- API::createAttachmentItem("linked_url", [], $key, $this, 'json');
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody(), true);
- $json['parentItem'] = $key;
- require_once '../../model/Utilities.inc.php';
- require_once '../../model/ID.inc.php';
- $json['key'] = \Zotero_ID::getKey();
- $json['version'] = 0;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert200ForObject($response);
- }
-
-
- public function testEditEmptyLinkAttachmentItem() {
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("linked_url", [], $key, $this, 'jsonData');
-
- $key = $json['key'];
- $version = $json['version'];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json')['data'];
- // Item shouldn't change
- $this->assertEquals($version, $json['version']);
-
- return $json;
- }
-
-
- public function testEditEmptyImportedURLAttachmentItem() {
- $key = API::createItem("book", false, $this, 'key');
- $json = API::createAttachmentItem("imported_url", [], $key, $this, 'jsonData');
-
- $key = $json['key'];
- $version = $json['version'];
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json')['data'];
- // Item shouldn't change
- $this->assertEquals($version, $json['version']);
-
- return $json;
- }
-
-
- /**
- * @depends testEditEmptyLinkAttachmentItem
- */
- public function testEditLinkAttachmentItem($json) {
- $key = $json['key'];
- $version = $json['version'];
-
- $contentType = "text/xml";
- $charset = "utf-8";
-
- $json['contentType'] = $contentType;
- $json['charset'] = $charset;
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$key",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
- $json = API::getItem($key, $this, 'json')['data'];
- $this->assertEquals($contentType, $json['contentType']);
- $this->assertEquals($charset, $json['charset']);
- }
-
- /**
- * @group attachments
- * @group classic-sync
- */
- public function testCreateLinkedFileAttachment() {
- $key = API::createItem("book", false, $this, 'key');
- $path = 'attachments:tést.txt';
- $json = API::createAttachmentItem(
- "linked_file", [
- 'path' => $path
- ], $key, $this, 'jsonData'
- );
- $this->assertEquals('linked_file', $json['linkMode']);
- // Linked file should have path
- $this->assertEquals($path, $json['path']);
- // And shouldn't have other attachment properties
- $this->assertArrayNotHasKey('filename', $json);
- $this->assertArrayNotHasKey('md5', $json);
- $this->assertArrayNotHasKey('mtime', $json);
-
- // Until classic sync is removed, paths should be stored as Mozilla-style relative descriptors,
- // at which point they should be batch converted
- require_once 'include/sync.inc.php';
- require_once '../../include/Unicode.inc.php';
- require_once '../../model/Attachments.inc.php';
- $sessionID = \Sync::login();
- $xml = \Sync::updated($sessionID, time() - 10);
- $path2 = (string) array_get_first($xml->xpath('//items/item[@key="' . $json['key'] . '"]/path'));
- $this->assertEquals(
- $path,
- "attachments:" . \Zotero_Attachments::decodeRelativeDescriptorString(substr($path2, 12))
- );
- }
-
- /**
- * @group attachments
- * @group classic-sync
- */
- public function testLinkedFileAttachmentPathViaSync() {
- require_once 'include/sync.inc.php';
- require_once '../../include/Unicode.inc.php';
- require_once '../../model/Attachments.inc.php';
- require_once '../../model/ID.inc.php';
-
- $sessionID = \Sync::login();
- $xml = \Sync::updated($sessionID, time());
-
- $updateKey = (string) $xml['updateKey'];
- $itemKey = \Zotero_ID::getKey();
- $filename = "tést.pdf";
-
- // Create item via sync
- $data = '- '
- // See note in testCreateLinkedFileAttachment
- . '
attachments:' . \Zotero_Attachments::encodeRelativeDescriptorString($filename) . ' '
- . ' ';
- $response = \Sync::upload($sessionID, $updateKey, $data);
- \Sync::waitForUpload($sessionID, $response, $this);
- \Sync::logout($sessionID);
-
- $json = API::getItem($itemKey, $this, 'json');
- $this->assertEquals('linked_file', $json['data']['linkMode']);
- // Linked file should have path
- $this->assertEquals("attachments:" . $filename, $json['data']['path']);
- }
-
- /**
- * @group attachments
- * @group classic-sync
- */
- public function testStoredFileAttachmentPathViaSync() {
- require_once 'include/sync.inc.php';
- require_once '../../include/Unicode.inc.php';
- require_once '../../model/Attachments.inc.php';
- require_once '../../model/ID.inc.php';
-
- $sessionID = \Sync::login();
- $xml = \Sync::updated($sessionID, time());
-
- $updateKey = (string) $xml['updateKey'];
- $itemKey = \Zotero_ID::getKey();
- $filename = "tést.pdf";
-
- // Create item via sync
- $data = '- '
- // See note in testCreateLinkedFileAttachment
- . '
storage:' . \Zotero_Attachments::encodeRelativeDescriptorString($filename) . ' '
- . ' ';
- $response = \Sync::upload($sessionID, $updateKey, $data);
- \Sync::waitForUpload($sessionID, $response, $this);
- \Sync::logout($sessionID);
-
- $json = API::getItem($itemKey, $this, 'json');
- $this->assertEquals('imported_file', $json['data']['linkMode']);
- // Linked file should have path
- $this->assertEquals($filename, $json['data']['filename']);
- }
-
- /**
- * Date Modified should be updated when a field is changed if not included in upload
- */
- public function testDateModifiedChangeOnEdit() {
- $json = API::createAttachmentItem("linked_file", [], false, $this, 'jsonData');
- $modified = $json['dateModified'];
- unset($json['dateModified']);
- $json['note'] = "Test";
-
- sleep(1);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $json['version'])
- );
- $this->assert204($response);
-
- $json = API::getItem($json['key'], $this, 'json')['data'];
- $this->assertNotEquals($modified, $json['dateModified']);
- }
-
- /**
- * Date Modified shouldn't be changed if 1) dateModified is provided or 2) certain fields are changed
- */
- public function testDateModifiedNoChange() {
- $collectionKey = API::createCollection('Test', false, $this, 'key');
-
- $json = API::createItem('book', false, $this, 'jsonData');
- $modified = $json['dateModified'];
-
- for ($i = 1; $i <= 5; $i++) {
- sleep(1);
-
- switch ($i) {
- case 1:
- $json['title'] = 'A';
- break;
-
- case 2:
- // For all subsequent tests, unset field, which would normally cause it to be updated
- unset($json['dateModified']);
-
- $json['collections'] = [$collectionKey];
- break;
-
- case 3:
- $json['deleted'] = true;
- break;
-
- case 4:
- $json['deleted'] = false;
- break;
-
- case 5:
- $json['tags'] = [
- [
- 'tag' => 'A'
- ]
- ];
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- [
- "If-Unmodified-Since-Version: " . $json['version'],
- // TODO: Remove
- [
- "User-Agent: Firefox"
- ]
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response)['successful'][0]['data'];
- $this->assertEquals($modified, $json['dateModified'], "Date Modified changed on loop $i");
- }
- }
-
- public function testEditAttachmentAtomUpdatedTimestamp() {
- $xml = API::createAttachmentItem("linked_file", [], false, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $atomUpdated = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $json = json_decode($data['content'], true);
- $json['note'] = "Test";
-
- sleep(1);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}",
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $data['version'])
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($data['key']);
- $atomUpdated2 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertNotEquals($atomUpdated2, $atomUpdated);
- }
-
-
- public function testEditAttachmentAtomUpdatedTimestampTmpZoteroClientHack() {
- $xml = API::createAttachmentItem("linked_file", [], false, $this, 'atom');
- $data = API::parseDataFromAtomEntry($xml);
- $atomUpdated = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $json = json_decode($data['content'], true);
- unset($json['dateModified']);
- $json['note'] = "Test";
-
- sleep(1);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/{$data['key']}",
- json_encode($json),
- [
- "If-Unmodified-Since-Version: " . $data['version'],
- // TODO: Remove
- [
- "User-Agent: Firefox"
- ]
- ]
- );
- $this->assert204($response);
-
- $xml = API::getItemXML($data['key']);
- $atomUpdated2 = (string) array_get_first($xml->xpath('//atom:entry/atom:updated'));
- $this->assertNotEquals($atomUpdated2, $atomUpdated);
- }
-
-
- public function testNewAttachmentItemInvalidLinkMode() {
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
-
- // Invalid linkMode
- $json->linkMode = "invalidName";
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'invalidName' is not a valid linkMode");
-
- // Missing linkMode
- unset($json->linkMode);
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'linkMode' property not provided");
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testNewAttachmentItemMD5OnLinkedURL($json) {
- $parentKey = $json['key'];
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $json->parentItem = $parentKey;
-
- $json->md5 = "c7487a750a97722ae1878ed46b215ebe";
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'md5' is valid only for imported attachment items");
- }
-
-
- /**
- * @depends testNewEmptyBookItem
- */
- public function testNewAttachmentItemModTimeOnLinkedURL($json) {
- $parentKey = $json['key'];
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $json = json_decode($response->getBody());
- $json->parentItem = $parentKey;
-
- $json->mtime = "1332807793000";
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "'mtime' is valid only for imported attachment items");
- }
-
-
- public function testCannotChangeStoragePropertiesInGroupLibraries() {
- $key = API::groupCreateItem(
- self::$config['ownedPrivateGroupID'], "book", [], $this, 'key'
- );
- $json = API::groupCreateAttachmentItem(
- self::$config['ownedPrivateGroupID'], "imported_url", [], $key, $this, 'jsonData'
- );
-
- $key = $json['key'];
- $version = $json['version'];
-
- $props = ["md5", "mtime"];
- foreach ($props as $prop) {
- $json2 = $json;
- $json2[$prop] = "new" . ucwords($prop);
- $response = API::groupPut(
- self::$config['ownedPrivateGroupID'],
- "items/$key",
- json_encode($json2),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert400($response);
- $this->assertEquals("Cannot change '$prop' directly in group library", $response->getBody());
- }
- }
-
-
- public function testMappedCreatorTypes() {
- $json = [
- [
- 'itemType' => 'presentation',
- 'title' => 'Test',
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "Foo"
- ]
- ]
- ],
- [
- 'itemType' => 'presentation',
- 'title' => 'Test',
- 'creators' => [
- [
- "creatorType" => "editor",
- "name" => "Foo"
- ]
- ]
- ]
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode($json)
- );
- // 'author' gets mapped automatically
- $this->assert200ForObject($response);
- // Others don't
- $this->assert400ForObject($response, false, 1);
- }
-
-
- public function testLibraryUser() {
- $json = API::createItem('book', false, $this, 'json');
- $this->assertEquals('user', $json['library']['type']);
- $this->assertEquals(self::$config['userID'], $json['library']['id']);
- $this->assertEquals(self::$config['username'], $json['library']['name']);
- $this->assertRegExp('%^https?://[^/]+/' . self::$config['username'] . '$%', $json['library']['links']['alternate']['href']);
- $this->assertEquals('text/html', $json['library']['links']['alternate']['type']);
- }
-
-
- public function testLibraryGroup() {
- $json = API::groupCreateItem(self::$config['ownedPrivateGroupID'], 'book', [], $this, 'json');
- $this->assertEquals('group', $json['library']['type']);
- $this->assertEquals(self::$config['ownedPrivateGroupID'], $json['library']['id']);
- $this->assertEquals(self::$config['ownedPrivateGroupName'], $json['library']['name']);
- $this->assertRegExp('%^https?://[^/]+/groups/[0-9]+$%', $json['library']['links']['alternate']['href']);
- $this->assertEquals('text/html', $json['library']['links']['alternate']['type']);
- }
-
-
- public function testNumChildrenJSON() {
- $json = API::createItem("book", false, $this, 'json');
- $this->assertEquals(0, $json['meta']['numChildren']);
- $key = $json['key'];
-
- API::createAttachmentItem("linked_url", [], $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(1, $json['meta']['numChildren']);
-
- API::createNoteItem("Test", $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(2, $json['meta']['numChildren']);
- }
-
-
- public function testNumChildrenAtom() {
- $xml = API::createItem("book", false, $this, 'atom');
- $this->assertEquals(0, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
- $data = API::parseDataFromAtomEntry($xml);
- $key = $data['key'];
-
- API::createAttachmentItem("linked_url", [], $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(1, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
-
- API::createNoteItem("Test", $key, $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
- }
-
-
- public function testTop() {
- API::userClear(self::$config['userID']);
-
- $collectionKey = API::createCollection('Test', false, $this, 'key');
- $emptyCollectionKey = API::createCollection('Empty', false, $this, 'key');
-
- $parentTitle1 = "Parent Title";
- $childTitle1 = "This is a Test Title";
- $parentTitle2 = "Another Parent Title";
- $parentTitle3 = "Yet Another Parent Title";
- $noteText = "This is a sample note.";
- $parentTitleSearch = "title";
- $childTitleSearch = "test";
- $dates = ["2013", "January 3, 2010", ""];
- $orderedDates = [$dates[2], $dates[1], $dates[0]];
- $itemTypes = ["journalArticle", "newspaperArticle", "book"];
-
- $parentKeys = [];
- $childKeys = [];
-
- $parentKeys[] = API::createItem($itemTypes[0], [
- 'title' => $parentTitle1,
- 'date' => $dates[0],
- 'collections' => [
- $collectionKey
- ]
- ], $this, 'key');
- $childKeys[] = API::createAttachmentItem("linked_url", [
- 'title' => $childTitle1
- ], $parentKeys[0], $this, 'key');
-
- $parentKeys[] = API::createItem($itemTypes[1], [
- 'title' => $parentTitle2,
- 'date' => $dates[1]
- ], $this, 'key');
- $childKeys[] = API::createNoteItem($noteText, $parentKeys[1], $this, 'key');
-
- // Create item with deleted child that matches child title search
- $parentKeys[] = API::createItem($itemTypes[2], [
- 'title' => $parentTitle3
- ], $this, 'key');
- API::createAttachmentItem("linked_url", [
- 'title' => $childTitle1,
- 'deleted' => true
- ], $parentKeys[sizeOf($parentKeys) - 1], $this, 'key');
-
- // Add deleted item with non-deleted child
- $deletedKey = API::createItem("book", [
- 'title' => "This is a deleted item",
- 'deleted' => true
- ], $this, 'key');
- API::createNoteItem("This is a child note of a deleted item.", $deletedKey, $this, 'key');
-
- // /top, JSON
- $response = API::userGet(
- self::$config['userID'],
- "items/top"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $done = [];
- foreach ($json as $item) {
- $this->assertContains($item['key'], $parentKeys);
- $this->assertNotContains($item['key'], $done);
- $done[] = $item['key'];
- }
-
- // /top, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $xpath);
- }
-
- // /top, JSON, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $this->assertEquals($parentKeys[0], $json[0]['key']);
-
- // /top, Atom, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?content=json"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, JSON, in empty collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$emptyCollectionKey/items/top"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- $this->assertTotalResults(0, $response);
-
- // /top, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(sizeOf($parentKeys), $keys);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $keys);
- }
-
- // /top, keys, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?format=keys"
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for parent, JSON
- $response = API::userGet(
- self::$config['userID'],
- "items/top?itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($parentKeys[0], $json[0]['key']);
-
- // /top with itemKey for parent, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for parent, JSON, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($parentKeys[0], $json[0]['key']);
-
- // /top with itemKey for parent, Atom, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?content=json&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for parent, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for parent, keys, in collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?format=keys&itemKey=" . $parentKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top with itemKey for child, JSON
- $response = API::userGet(
- self::$config['userID'],
- "items/top?itemKey=" . $childKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($parentKeys[0], $json[0]['key']);
-
- // /top with itemKey for child, Atom
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&itemKey=" . $childKeys[0]
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertEquals($parentKeys[0], (string) array_shift($xpath));
-
- // /top with itemKey for child, keys
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&itemKey=" . $childKeys[0]
- );
- $this->assert200($response);
- $this->assertEquals($parentKeys[0], trim($response->getBody()));
-
- // /top, Atom, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $done = [];
- foreach ($json as $item) {
- $this->assertContains($item['key'], $parentKeys);
- $this->assertNotContains($item['key'], $done);
- $done[] = $item['key'];
- }
-
- // /top, Atom, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- foreach ($parentKeys as $parentKey) {
- $this->assertContains($parentKey, $xpath);
- }
-
- // /top, JSON, in collection, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertContains($parentKeys[0], $json[0]['key']);
-
- // /top, Atom, in collection, with q for all items
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?content=json&q=$parentTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, JSON, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertContains($parentKeys[0], $json[0]['key']);
-
- // /top, Atom, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);
-
- // /top, JSON, in collection, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- // Not currently possible
- /*$this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);*/
-
- // /top, Atom, in collection, with q for child item
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?content=json&q=$childTitleSearch"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- // Not currently possible
- /*$this->assertNumResults(1, $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertCount(1, $xpath);
- $this->assertContains($parentKeys[0], $xpath);*/
-
- // /top, JSON, with q for all items, ordered by title
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- . "&order=title"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $returnedTitles = [];
- foreach ($json as $item) {
- $returnedTitles[] = $item['data']['title'];
- }
- $orderedTitles = [$parentTitle1, $parentTitle2, $parentTitle3];
- sort($orderedTitles);
- $this->assertEquals($orderedTitles, $returnedTitles);
-
- // /top, Atom, with q for all items, ordered by title
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- . "&order=title"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:title');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedTitles = [$parentTitle1, $parentTitle2, $parentTitle3];
- sort($orderedTitles);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedTitles, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by date asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- . "&order=date&sort=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $orderedResults = array_map(function ($val) {
- return $val['data']['date'];
- }, $json);
- $this->assertEquals($orderedDates, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by date asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- . "&order=date&sort=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:content');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedResults = array_map(function ($val) {
- return json_decode($val)->date;
- }, $xpath);
- $this->assertEquals($orderedDates, $orderedResults);
-
- // /top, JSON, with q for all items, ordered by date desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- . "&order=date&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $orderedDatesReverse = array_reverse($orderedDates);
- $orderedResults = array_map(function ($val) {
- return $val['data']['date'];
- }, $json);
- $this->assertEquals($orderedDatesReverse, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by date desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- . "&order=date&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/atom:content');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedDatesReverse = array_reverse($orderedDates);
- $orderedResults = array_map(function ($val) {
- return json_decode($val)->date;
- }, $xpath);
- $this->assertEquals($orderedDatesReverse, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- . "&order=itemType"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $orderedItemTypes = $itemTypes;
- sort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return $val['data']['itemType'];
- }, $json);
- $this->assertEquals($orderedItemTypes, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type asc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- . "&order=itemType"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:itemType');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedItemTypes = $itemTypes;
- sort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedItemTypes, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?q=$parentTitleSearch"
- . "&order=itemType&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $json = API::getJSONFromResponse($response);
- $orderedItemTypes = $itemTypes;
- rsort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return $val['data']['itemType'];
- }, $json);
- $this->assertEquals($orderedItemTypes, $orderedResults);
-
- // /top, Atom, with q for all items, ordered by item type desc
- $response = API::userGet(
- self::$config['userID'],
- "items/top?content=json&q=$parentTitleSearch"
- . "&order=itemType&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($parentKeys), $response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:itemType');
- $this->assertCount(sizeOf($parentKeys), $xpath);
- $orderedItemTypes = $itemTypes;
- rsort($orderedItemTypes);
- $orderedResults = array_map(function ($val) {
- return (string) $val;
- }, $xpath);
- $this->assertEquals($orderedItemTypes, $orderedResults);
- }
-
-
- public function testIncludeTrashed() {
- API::userClear(self::$config['userID']);
-
- $key1 = API::createItem("book", false, $this, 'key');
- $key2 = API::createItem("book", [
- "deleted" => 1
- ], $this, 'key');
- $key3 = API::createNoteItem("", $key1, $this, 'key');
-
- // All three items should show up with includeTrashed=1
- $response = API::userGet(
- self::$config['userID'],
- "items?includeTrashed=1"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(3, $json);
- $keys = [$json[0]['key'], $json[1]['key'], $json[2]['key']];
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
- $this->assertContains($key3, $keys);
-
- // ?itemKey should show the deleted item
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=$key2,$key3&includeTrashed=1"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json);
- $keys = [$json[0]['key'], $json[1]['key']];
- $this->assertContains($key2, $keys);
- $this->assertContains($key3, $keys);
-
- // /top should show the deleted item
- $response = API::userGet(
- self::$config['userID'],
- "items/top?includeTrashed=1"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json);
- $keys = [$json[0]['key'], $json[1]['key']];
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
- }
-
-
- public function testTrash() {
- API::userClear(self::$config['userID']);
-
- $key1 = API::createItem("book", false, $this, 'key');
- $key2 = API::createItem("book", [
- "deleted" => 1
- ], $this, 'key');
-
- // Item should show up in trash
- $response = API::userGet(
- self::$config['userID'],
- "items/trash"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $this->assertEquals($key2, $json[0]['key']);
-
- // And not show up in main items
- $response = API::userGet(
- self::$config['userID'],
- "items"
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $this->assertEquals($key1, $json[0]['key']);
-
- // Including with ?itemKey
- $response = API::userGet(
- self::$config['userID'],
- "items?itemKey=" . $key2
- );
- $json = API::getJSONFromResponse($response);
- $this->assertCount(0, $json);
- }
-
-
- public function testParentItem() {
- $json = API::createItem("book", false, $this, 'jsonData');
- $parentKey = $json['key'];
- $parentVersion = $json['version'];
-
- $json = API::createAttachmentItem("linked_file", [], $parentKey, $this, 'jsonData');
- $childKey = $json['key'];
- $childVersion = $json['version'];
-
- $this->assertArrayHasKey('parentItem', $json);
- $this->assertEquals($parentKey, $json['parentItem']);
-
- // Remove the parent, making the child a standalone attachment
- unset($json['parentItem']);
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$childKey",
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $childVersion)
- );
- $this->assert204($response);
-
- $json = API::getItem($childKey, $this, 'json')['data'];
- $this->assertArrayNotHasKey('parentItem', $json);
- }
-
-
- public function testParentItemPatch() {
- $json = API::createItem("book", false, $this, 'jsonData');
- $parentKey = $json['key'];
- $parentVersion = $json['version'];
-
- $json = API::createAttachmentItem("linked_file", [], $parentKey, $this, 'jsonData');
- $childKey = $json['key'];
- $childVersion = $json['version'];
-
- $this->assertArrayHasKey('parentItem', $json);
- $this->assertEquals($parentKey, $json['parentItem']);
-
- $json = array(
- 'title' => 'Test'
- );
-
- // With PATCH, parent shouldn't be removed even though unspecified
- $response = API::userPatch(
- self::$config['userID'],
- "items/$childKey",
- json_encode($json),
- array("If-Unmodified-Since-Version: " . $childVersion)
- );
- $this->assert204($response);
-
- $json = API::getItem($childKey, $this, 'json')['data'];
- $this->assertArrayHasKey('parentItem', $json);
- $childVersion = $json['version'];
-
- // But it should be removed with parentItem: false
- $json = [
- 'parentItem' => false
- ];
- $response = API::userPatch(
- self::$config['userID'],
- "items/$childKey",
- json_encode($json),
- ["If-Unmodified-Since-Version: " . $childVersion]
- );
- $this->assert204($response);
- $json = API::getItem($childKey, $this, 'json')['data'];
- $this->assertArrayNotHasKey('parentItem', $json);
- }
-
-
- public function test_should_return_409_on_missing_parent() {
- $missingParentKey = "BDARG2AV";
- $json = API::createNoteItem("test
", $missingParentKey, $this);
- $this->assert409ForObject($json, "Parent item $missingParentKey not found");
- $this->assertEquals($missingParentKey, $json['failed'][0]['data']['parentItem']);
- }
-
-
- public function test_should_return_409_on_missing_parent_if_parent_failed() {
- // Collection
- $collectionKey = API::createCollection("A", null, $this, 'key');
-
- $version = API::getLibraryVersion();
- $parentKey = "BDARG2AV";
- $tag = \Zotero_Utilities::randomString(300);
-
- // Parent item
- $item1JSON = API::getItemTemplate("book");
- $item1JSON->key = $parentKey;
- $item1JSON->creators = [
- [
- "firstName" => "A.",
- "lastName" => "Nespola",
- "creatorType" => "author"
- ]
- ];
- $item1JSON->tags = [
- [
- "tag" => "A"
- ],
- [
- "tag" => $tag
- ]
- ];
- $item1JSON->collections = [$collectionKey];
- // Child note
- $item2JSON = API::getItemTemplate("note");
- $item2JSON->parentItem = $parentKey;
- // Child attachment with note
- // TODO: Use template function
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $item3JSON = json_decode($response->getBody());
- $item3JSON->parentItem = $parentKey;
- $item3JSON->note = "Test";
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$item1JSON, $item2JSON, $item3JSON]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert413ForObject($json, null, 0);
- $this->assert409ForObject($json, "Parent item $parentKey not found", 1);
- $this->assertEquals($parentKey, $json['failed'][1]['data']['parentItem']);
- $this->assert409ForObject($json, "Parent item $parentKey not found", 2);
- $this->assertEquals($parentKey, $json['failed'][2]['data']['parentItem']);
- }
-
-
- public function test_should_return_409_on_missing_collection() {
- $missingCollectionKey = "BDARG2AV";
- $json = API::createItem("book", [ 'collections' => [$missingCollectionKey] ], $this);
- $this->assert409ForObject($json, "Collection $missingCollectionKey not found");
- $this->assertEquals($missingCollectionKey, $json['failed'][0]['data']['collection']);
- }
-
-
- public function test_should_allow_emoji_in_title() {
- $title = "🐶"; // 4-byte character
-
- $key = API::createItem("book", array("title" => $title), $this, 'key');
-
- // Test entry (JSON)
- $response = API::userGet(
- self::$config['userID'],
- "items/$key"
- );
- $this->assertContains("\"title\": \"$title\"", $response->getBody());
-
- // Test feed (JSON)
- $response = API::userGet(
- self::$config['userID'],
- "items"
- );
- $this->assertContains("\"title\": \"$title\"", $response->getBody());
-
- // Test entry (Atom)
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?content=json"
- );
- $this->assertContains("\"title\": \"$title\"", $response->getBody());
-
- // Test feed (Atom)
- $response = API::userGet(
- self::$config['userID'],
- "items?content=json"
- );
- $this->assertContains("\"title\": \"$title\"", $response->getBody());
- }
-}
diff --git a/tests/remote/tests/API/3/KeysTest.php b/tests/remote/tests/API/3/KeysTest.php
deleted file mode 100644
index 41e9717d..00000000
--- a/tests/remote/tests/API/3/KeysTest.php
+++ /dev/null
@@ -1,303 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class KeysTest extends APITests {
- public function testGetKeys() {
- // No anonymous access
- API::useAPIKey("");
- $response = API::userGet(
- self::$config['userID'],
- 'keys'
- );
- $this->assert403($response);
-
- // No access with user's API key
- API::useAPIKey(self::$config['apiKey']);
- $response = API::userGet(
- self::$config['userID'],
- 'keys'
- );
- $this->assert403($response);
-
- // Root access
- $response = API::userGet(
- self::$config['userID'],
- 'keys',
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertTrue(is_array($json));
- $this->assertTrue(sizeOf($json) > 0);
- $this->assertArrayHasKey('dateAdded', $json[0]);
- $this->assertArrayHasKey('lastUsed', $json[0]);
- $this->assertArrayHasKey('recentIPs', $json[0]);
- }
-
-
- public function testGetKeyInfoCurrent() {
- API::useAPIKey("");
- $response = API::get(
- 'keys/current',
- [
- "Zotero-API-Key" => self::$config['apiKey']
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(self::$config['apiKey'], $json['key']);
- $this->assertEquals(self::$config['userID'], $json['userID']);
- $this->arrayHasKey("user", $json['access']);
- $this->arrayHasKey("groups", $json['access']);
- $this->assertTrue($json['access']['user']['library']);
- $this->assertTrue($json['access']['user']['files']);
- $this->assertTrue($json['access']['user']['notes']);
- $this->assertTrue($json['access']['user']['write']);
- $this->assertTrue($json['access']['groups']['all']['library']);
- $this->assertTrue($json['access']['groups']['all']['write']);
- $this->assertArrayNotHasKey('name', $json);
- $this->assertArrayNotHasKey('dateAdded', $json);
- $this->assertArrayNotHasKey('lastUsed', $json);
- $this->assertArrayNotHasKey('recentIPs', $json);
- }
-
-
- public function testGetKeyInfoCurrentWithoutHeader() {
- API::useAPIKey("");
- $response = API::get('keys/current');
- $this->assert403($response);
- }
-
-
- public function testGetKeyInfoByPath() {
- API::useAPIKey("");
- $response = API::get('keys/' . self::$config['apiKey']);
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(self::$config['apiKey'], $json['key']);
- $this->assertEquals(self::$config['userID'], $json['userID']);
- $this->arrayHasKey("user", $json['access']);
- $this->arrayHasKey("groups", $json['access']);
- $this->assertTrue($json['access']['user']['library']);
- $this->assertTrue($json['access']['user']['files']);
- $this->assertTrue($json['access']['user']['notes']);
- $this->assertTrue($json['access']['user']['write']);
- $this->assertTrue($json['access']['groups']['all']['library']);
- $this->assertTrue($json['access']['groups']['all']['write']);
- $this->assertArrayNotHasKey('name', $json);
- $this->assertArrayNotHasKey('dateAdded', $json);
- $this->assertArrayNotHasKey('lastUsed', $json);
- $this->assertArrayNotHasKey('recentIPs', $json);
- }
-
-
- // Deprecated
- public function testGetKeyInfoWithUser() {
- API::useAPIKey("");
- $response = API::userGet(
- self::$config['userID'],
- 'keys/' . self::$config['apiKey']
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(self::$config['apiKey'], $json['key']);
- $this->assertEquals(self::$config['userID'], $json['userID']);
- $this->arrayHasKey("user", $json['access']);
- $this->arrayHasKey("groups", $json['access']);
- $this->assertTrue($json['access']['user']['library']);
- $this->assertTrue($json['access']['user']['files']);
- $this->assertTrue($json['access']['user']['notes']);
- $this->assertTrue($json['access']['user']['write']);
- $this->assertTrue($json['access']['groups']['all']['library']);
- $this->assertTrue($json['access']['groups']['all']['write']);
- }
-
-
- public function testKeyCreateAndDelete() {
- API::useAPIKey("");
-
- $name = "Test " . uniqid();
-
- // Can't create anonymously
- $response = API::userPost(
- self::$config['userID'],
- 'keys',
- json_encode([
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ])
- );
- $this->assert403($response);
-
- // Create as root
- $response = API::userPost(
- self::$config['userID'],
- 'keys',
- json_encode([
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ]),
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert201($response);
- $json = API::getJSONFromResponse($response);
- $key = $json['key'];
- $this->assertEquals($json['name'], $name);
- $this->assertEquals(['user' => ['library' => true, 'files' => true]], $json['access']);
-
- // Delete anonymously (with embedded key)
- $response = API::userDelete(
- self::$config['userID'],
- "keys/current",
- [
- "Zotero-API-Key" => $key
- ]
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "keys/current",
- [
- "Zotero-API-Key" => $key
- ]
- );
- $this->assert403($response);
- }
-
-
- // Private API
- public function testKeyCreateAndModifyWithCredentials() {
- API::useAPIKey("");
-
- $name = "Test " . uniqid();
-
- // Can't create on /users/:userID/keys with credentials
- $response = API::userPost(
- self::$config['userID'],
- 'keys',
- json_encode([
- 'username' => self::$config['username'],
- 'password' => self::$config['password'],
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ])
- );
- $this->assert403($response);
-
- // Create with credentials
- $response = API::post(
- 'keys',
- json_encode([
- 'username' => self::$config['username'],
- 'password' => self::$config['password'],
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ]),
- [],
- []
- );
- $this->assert201($response);
- $json = API::getJSONFromResponse($response);
- $key = $json['key'];
- $this->assertEquals($json['userID'], self::$config['userID']);
- $this->assertEquals($json['name'], $name);
- $this->assertEquals(['user' => ['library' => true, 'files' => true]], $json['access']);
-
- $name = "Test " . uniqid();
-
- // Can't modify on /users/:userID/keys/:key with credentials
- $response = API::userPut(
- self::$config['userID'],
- "keys/$key",
- json_encode([
- 'username' => self::$config['username'],
- 'password' => self::$config['password'],
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ])
- );
- $this->assert403($response);
-
- // Modify with credentials
- $response = API::put(
- "keys/$key",
- json_encode([
- 'username' => self::$config['username'],
- 'password' => self::$config['password'],
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ])
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $key = $json['key'];
- $this->assertEquals($json['name'], $name);
-
- $response = API::userDelete(
- self::$config['userID'],
- "keys/$key"
- );
- $this->assert204($response);
- }
-}
diff --git a/tests/remote/tests/API/3/MappingsTest.php b/tests/remote/tests/API/3/MappingsTest.php
deleted file mode 100644
index 52462d00..00000000
--- a/tests/remote/tests/API/3/MappingsTest.php
+++ /dev/null
@@ -1,82 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class MappingsTests extends APITests {
- public function testNewItem() {
- $response = API::get("items/new?itemType=invalidItemType");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=book");
- $this->assert200($response);
- $this->assertContentType('application/json', $response);
- $json = json_decode($response->getBody());
- $this->assertEquals('book', $json->itemType);
- }
-
-
- public function testNewItemAttachment() {
- $response = API::get("items/new?itemType=attachment");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=attachment&linkMode=invalidLinkMode");
- $this->assert400($response);
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_url");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
- $this->assertObjectHasAttribute('url', $json);
-
- $response = API::get("items/new?itemType=attachment&linkMode=linked_file");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertNotNull($json);
- $this->assertObjectNotHasAttribute('url', $json);
- }
-
- public function testComputerProgramVersion() {
- $response = API::get("items/new?itemType=computerProgram");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertObjectHasAttribute('versionNumber', $json);
- $this->assertObjectNotHasAttribute('version', $json);
-
- $response = API::get("itemTypeFields?itemType=computerProgram");
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $fields = array_map(function ($val) {
- return $val->field;
- }, $json);
- $this->assertContains('versionNumber', $fields);
- $this->assertNotContains('version', $fields);
- }
-}
-?>
\ No newline at end of file
diff --git a/tests/remote/tests/API/3/NoteTest.php b/tests/remote/tests/API/3/NoteTest.php
deleted file mode 100644
index 99476d5b..00000000
--- a/tests/remote/tests/API/3/NoteTest.php
+++ /dev/null
@@ -1,193 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class NoteTests extends APITests {
- private $content;
- private $json;
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
- public function setUp() {
- parent::setUp();
-
- // Create too-long note content
- $this->content = str_repeat("1234567890", 25001);
-
- // Create JSON template
- $this->json = API::getItemTemplate("note");
- $this->json->note = $this->content;
- }
-
-
- public function test_utf8mb4_note() {
- $note = "🐻
"; // 4-byte character
- $json = API::getItemTemplate('note');
- $json->note = $note;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
-
- $this->assert200ForObject($response);
-
- $json = API::getJSONFromResponse($response);
- $json = $json['successful'][0]['data'];
- $this->assertSame($note, $json['note']);
- }
-
-
- public function testNoteTooLong() {
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$this->json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123456789...' too long"
- );
- }
-
- // Blank first two lines
- public function testNoteTooLongBlankFirstLines() {
- $this->json->note = " \n \n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$this->json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123456789...' too long"
- );
- }
-
-
- public function testNoteTooLongBlankFirstLinesHTML() {
- $this->json->note = "\n
\n
\n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$this->json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '1234567890123456789012345678901234567890123456789012345678901234567890123...' too long"
- );
- }
-
-
- public function testNoteTooLongTitlePlusNewlines() {
- $this->json->note = "Full Text:\n\n" . $this->content;
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$this->json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note 'Full Text: 1234567890123456789012345678901234567890123456789012345678901234567...' too long"
- );
- }
-
-
- // All content within HTML tags
- public function testNoteTooLongWithinHTMLTags() {
- $this->json->note = " \n";
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$this->json]),
- array("Content-Type: application/json")
- );
- $this->assert413ForObject(
- $response,
- "Note '<p><!-- 1234567890123456789012345678901234567890123456789012345678901234...' too long"
- );
- }
-
-
- public function testSaveHTML() {
- $content = 'Foo & Bar
';
- $json = API::createNoteItem($content, false, $this, 'json');
- $this->assertEquals($content, $json['data']['note']);
- }
-
-
- public function testSaveHTMLAtom() {
- $content = 'Foo & Bar
';
- $xml = API::createNoteItem($content, false, $this, 'atom');
- $this->assertEquals($content, json_decode($xml->content)->note);
- }
-
-
- public function testSaveUnchangedSanitizedNote() {
- $json = API::createNoteItem("Foo", false, $this, 'json');
- $response = API::postItem($json['data']);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey(0, $json['unchanged']);
- }
-
-
- public function test_should_allow_zotero_links_in_notes() {
- $json = API::createNoteItem('Test
', false, $this, 'json');
-
- $val = '';
- $json['data']['note'] = $val;
-
- $response = API::postItem($json['data']);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($val, $json['successful'][0]['data']['note']);
- }
-}
-?>
diff --git a/tests/remote/tests/API/3/NotificationsTest.php b/tests/remote/tests/API/3/NotificationsTest.php
deleted file mode 100644
index b3b27c68..00000000
--- a/tests/remote/tests/API/3/NotificationsTest.php
+++ /dev/null
@@ -1,562 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2015 Zotero
- https://www.zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-/**
- * @group sns
- */
-class NotificationsTests extends APITests {
- public function testNewItemNotification() {
- $response = API::createItem("book", false, $this, 'response');
- $version = API::getJSONFromResponse($response)['successful'][0]['version'];
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicUpdated',
- 'topic' => '/users/' . self::$config['userID'],
- 'version' => $version
- ], $response);
- }
-
-
- public function testModifyItemNotification() {
- $json = API::createItem("book", false, $this, 'jsonData');
- $json['title'] = 'test';
- $response = API::userPut(
- self::$config['userID'],
- "items/{$json['key']}",
- json_encode($json)
- );
- $version = $response->getHeader('Last-Modified-Version');
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicUpdated',
- 'topic' => '/users/' . self::$config['userID'],
- 'version' => $version
- ], $response);
- }
-
-
- public function testDeleteItemNotification() {
- $json = API::createItem("book", false, $this, 'json');
- $response = API::userDelete(
- self::$config['userID'],
- "items/{$json['key']}",
- [
- "If-Unmodified-Since-Version: {$json['version']}"
- ]
- );
- $version = $response->getHeader('Last-Modified-Version');
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicUpdated',
- 'topic' => '/users/' . self::$config['userID'],
- 'version' => $version
- ], $response);
- }
-
-
- public function testKeyCreateNotification() {
- API::useAPIKey("");
-
- $name = "Test " . uniqid();
- $response = API::superPost(
- 'users/' . self::$config['userID'] . '/keys',
- json_encode([
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ])
- );
- try {
- // No notification when creating a new key
- $this->assertCountNotifications(0, $response);
- }
- // Clean up
- finally {
- $json = API::getJSONFromResponse($response);
- $key = $json['key'];
- $response = API::userDelete(
- self::$config['userID'],
- "keys/$key"
- );
- }
- }
-
-
- /**
- * Grant an API key access to a group
- */
- public function testKeyAddLibraryNotification() {
- API::useAPIKey("");
-
- $name = "Test " . uniqid();
- $json = [
- 'name' => $name,
- 'access' => [
- 'user' => [
- 'library' => true
- ]
- ]
- ];
-
- $response = API::superPost(
- 'users/' . self::$config['userID'] . '/keys?showid=1',
- json_encode($json)
- );
- $this->assert201($response);
- try {
- $json = API::getJSONFromResponse($response);
- $apiKey = $json['key'];
- $apiKeyID = $json['id'];
-
- // Add a group to the key, which should trigger topicAdded
- $json['access']['groups'][self::$config['ownedPrivateGroupID']] = [
- 'library' => true,
- 'write' => true
- ];
- $response = API::superPut(
- "keys/$apiKey",
- json_encode($json)
- );
- $this->assert200($response);
-
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicAdded',
- 'apiKeyID' => $apiKeyID,
- 'topic' => '/groups/' . self::$config['ownedPrivateGroupID']
- ], $response);
- }
- // Clean up
- finally {
- $response = API::superDelete("keys/$apiKey");
- }
- }
-
-
- /**
- * Make a group public, which should trigger topicAdded
- */
-
- /**
- * Show public groups in topic list for single-key requests
- */
-
- /**
- * Revoke access for a group from an API key
- */
- public function testKeyRemoveLibraryNotification() {
- API::useAPIKey("");
-
- $json = $this->createKey(self::$config['userID'], [
- 'user' => [
- 'library' => true
- ],
- 'groups' => [
- self::$config['ownedPrivateGroupID'] => [
- 'library' => true
- ]
- ]
- ]);
- $apiKey = $json['key'];
- $apiKeyID = $json['id'];
-
- try {
- // Remove group from the key, which should trigger topicRemoved
- unset($json['access']['groups']);
- $response = API::superPut(
- "keys/$apiKey",
- json_encode($json)
- );
- $this->assert200($response);
-
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicRemoved',
- 'apiKeyID' => $apiKeyID,
- 'topic' => '/groups/' . self::$config['ownedPrivateGroupID']
- ], $response);
- }
- // Clean up
- finally {
- $response = API::superDelete("keys/$apiKey");
- }
- }
-
-
- /**
- * Grant access to all groups to an API key that has access to a single group
- */
- public function testKeyAddAllGroupsToNoneNotification() {
- API::useAPIKey("");
-
- $json = $this->createKey(self::$config['userID'], [
- 'user' => [
- 'library' => true
- ]
- ]);
- $apiKey = $json['key'];
- $apiKeyID = $json['id'];
-
- try {
- // Get list of available groups
- $response = API::superGet('users/' . self::$config['userID'] . '/groups');
- $groupIDs = array_map(function ($group) {
- return $group['id'];
- }, API::getJSONFromResponse($response));
-
- // Add all groups to the key, which should trigger topicAdded for each groups
- $json['access']['groups'][0] = [
- 'library' => true
- ];
- $response = API::superPut(
- "keys/$apiKey",
- json_encode($json)
- );
- $this->assert200($response);
-
- $this->assertCountNotifications(sizeOf($groupIDs), $response);
- foreach ($groupIDs as $groupID) {
- $this->assertHasNotification([
- 'event' => 'topicAdded',
- 'apiKeyID' => $apiKeyID,
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Clean up
- finally {
- $response = API::superDelete("keys/$apiKey");
- }
- }
-
-
- /**
- * Grant access to all groups to an API key that has access to a single group
- */
- public function testKeyAddAllGroupsToOneNotification() {
- API::useAPIKey("");
-
- $json = $this->createKey(self::$config['userID'], [
- 'user' => [
- 'library' => true
- ],
- 'groups' => [
- self::$config['ownedPrivateGroupID'] => [
- 'library' => true
- ]
- ]
- ]);
- $apiKey = $json['key'];
- $apiKeyID = $json['id'];
-
- try {
- // Get list of available groups
- $response = API::superGet('users/' . self::$config['userID'] . '/groups');
- $groupIDs = array_map(function ($group) {
- return $group['id'];
- }, API::getJSONFromResponse($response));
- // Remove group that already had access
- $groupIDs = array_diff($groupIDs, [self::$config['ownedPrivateGroupID']]);
-
- // Add all groups to the key, which should trigger topicAdded for each new group
- // but not the group that was previously accessible
- unset($json['access']['groups'][self::$config['ownedPrivateGroupID']]);
- $json['access']['groups']['all'] = [
- 'library' => true
- ];
- $response = API::superPut(
- "keys/$apiKey",
- json_encode($json)
- );
- $this->assert200($response);
-
- $this->assertCountNotifications(sizeOf($groupIDs), $response);
- foreach ($groupIDs as $groupID) {
- $this->assertHasNotification([
- 'event' => 'topicAdded',
- 'apiKeyID' => $apiKeyID,
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Clean up
- finally {
- $response = API::superDelete("keys/$apiKey");
- }
- }
-
-
- /**
- * Revoke access for a group from an API key that has access to all groups
- */
- public function testKeyRemoveLibraryFromAllGroupsNotification() {
- API::useAPIKey("");
-
- $removedGroup = self::$config['ownedPrivateGroupID'];
-
- $json = $this->createKeyWithAllGroupAccess(self::$config['userID']);
- $apiKey = $json['key'];
- $apiKeyID = $json['id'];
-
- try {
- // Get list of available groups
- API::useAPIKey($apiKey);
- $response = API::userGet(
- self::$config['userID'],
- 'groups'
- );
- $groupIDs = array_map(function ($group) {
- return $group['id'];
- }, API::getJSONFromResponse($response));
-
- // Remove one group, and replace access array with new set
- $groupIDs = array_diff($groupIDs, [$removedGroup]);
- unset($json['access']['groups']['all']);
- foreach ($groupIDs as $groupID) {
- $json['access']['groups'][$groupID]['library'] = true;
- }
-
- // Post new JSON, which should trigger topicRemoved for the removed group
- API::useAPIKey("");
- $response = API::superPut(
- "keys/$apiKey",
- json_encode($json)
- );
- $this->assert200($response);
-
- $this->assertCountNotifications(1, $response);
- foreach ($groupIDs as $groupID) {
- $this->assertHasNotification([
- 'event' => 'topicRemoved',
- 'apiKeyID' => $apiKeyID,
- 'topic' => '/groups/' . $removedGroup
- ], $response);
- }
- }
- // Clean up
- finally {
- $response = API::superDelete("keys/$apiKey");
- }
- }
-
-
- /**
- * Create and delete group owned by user
- */
- public function testAddDeleteOwnedGroupNotification() {
- API::useAPIKey("");
-
- $json = $this->createKeyWithAllGroupAccess(self::$config['userID']);
- $apiKey = $json['key'];
-
- try {
- $allGroupsKeys = $this->getKeysWithAllGroupAccess(self::$config['userID']);
-
- // Create new group owned by user
- $response = $this->createGroup(self::$config['userID']);
- $xml = API::getXMLFromResponse($response);
- $groupID = (int) $xml->xpath("/atom:entry/zapi:groupID")[0];
-
- try {
- $this->assertCountNotifications(sizeOf($allGroupsKeys), $response);
- foreach ($allGroupsKeys as $key) {
- $response2 = API::superGet("keys/$key?showid=1");
- $json2 = API::getJSONFromResponse($response2);
- $this->assertHasNotification([
- 'event' => 'topicAdded',
- 'apiKeyID' => $json2['id'],
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Delete group
- finally {
- $response = API::superDelete("groups/$groupID");
- $this->assert204($response);
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicDeleted',
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Delete key
- finally {
- $response = API::superDelete("keys/$apiKey");
- try {
- $this->assert204($response);
- }
- catch (Exception $e) {
- var_dump($e);
- }
- }
- }
-
-
- public function testAddRemoveGroupMemberNotification() {
- API::useAPIKey("");
-
- $json = $this->createKeyWithAllGroupAccess(self::$config['userID']);
- $apiKey = $json['key'];
-
- try {
- // Get all keys with access to all groups
- $allGroupsKeys = $this->getKeysWithAllGroupAccess(self::$config['userID']);
-
- // Create group owned by another user
- $response = $this->createGroup(self::$config['userID2']);
- $xml = API::getXMLFromResponse($response);
- $groupID = (int) $xml->xpath("/atom:entry/zapi:groupID")[0];
-
- try {
- // Add user to group
- $response = API::superPost(
- "groups/$groupID/users",
- ' '
- );
- $this->assert200($response);
- $this->assertCountNotifications(sizeOf($allGroupsKeys), $response);
- foreach ($allGroupsKeys as $key) {
- $response2 = API::superGet("keys/$key?showid=1");
- $json2 = API::getJSONFromResponse($response2);
- $this->assertHasNotification([
- 'event' => 'topicAdded',
- 'apiKeyID' => $json2['id'],
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
-
- // Remove user from group
- $response = API::superDelete("groups/$groupID/users/" . self::$config['userID']);
- $this->assert204($response);
- $this->assertCountNotifications(sizeOf($allGroupsKeys), $response);
- foreach ($allGroupsKeys as $key) {
- $response2 = API::superGet("keys/$key?showid=1");
- $json2 = API::getJSONFromResponse($response2);
- $this->assertHasNotification([
- 'event' => 'topicRemoved',
- 'apiKeyID' => $json2['id'],
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Delete group
- finally {
- $response = API::superDelete("groups/$groupID");
- $this->assert204($response);
- $this->assertCountNotifications(1, $response);
- $this->assertHasNotification([
- 'event' => 'topicDeleted',
- 'topic' => '/groups/' . $groupID
- ], $response);
- }
- }
- // Delete key
- finally {
- $response = API::superDelete("keys/$apiKey");
- try {
- $this->assert204($response);
- }
- catch (Exception $e) {
- var_dump($e);
- }
- }
- }
-
-
- //
- // Private functions
- //
- private function createKey($userID, $access) {
- $name = "Test " . uniqid();
- $json = [
- 'name' => $name,
- 'access' => $access
- ];
- $response = API::superPost(
- "users/$userID/keys?showid=1",
- json_encode($json)
- );
- $this->assert201($response);
- $json = API::getJSONFromResponse($response);
- return $json;
- }
-
- private function createKeyWithAllGroupAccess($userID) {
- return $this->createKey($userID, [
- 'user' => [
- 'library' => true
- ],
- 'groups' => [
- 'all' => [
- 'library' => true
- ]
- ]
- ]);
- }
-
- private function createGroup($ownerID) {
- // Create new group owned by another
- $xml = new \SimpleXMLElement(' ');
- $xml['owner'] = $ownerID;
- $xml['name'] = 'Test';
- $xml['type'] = 'Private';
- $xml['libraryEditing'] = 'admins';
- $xml['libraryReading'] = 'members';
- $xml['fileEditing'] = 'admins';
- $xml['description'] = 'This is a description';
- $xml['url'] = '';
- $xml['hasImage'] = 0;
- $xml = $xml->asXML();
- $response = API::superPost(
- 'groups',
- $xml
- );
- $this->assert201($response);
- return $response;
- }
-
- private function getKeysWithAllGroupAccess($userID) {
- $response = API::superGet("users/$userID/keys");
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- return array_map(
- function ($keyObj) {
- return $keyObj['key'];
- },
- array_filter($json, function ($keyObj) {
- return !empty($keyObj['access']['groups']['all']['library']);
- })
- );
- }
-}
diff --git a/tests/remote/tests/API/3/ObjectTest.php b/tests/remote/tests/API/3/ObjectTest.php
deleted file mode 100644
index 2e94f88f..00000000
--- a/tests/remote/tests/API/3/ObjectTest.php
+++ /dev/null
@@ -1,785 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class ObjectTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
- public function tearDown() {
- API::userClear(self::$config['userID']);
- }
-
-
- public function testMultiObjectGet() {
- $this->_testMultiObjectGet('collection');
- $this->_testMultiObjectGet('item');
- $this->_testMultiObjectGet('search');
- }
-
- public function testCreateByPut() {
- $this->_testCreateByPut('collection');
- $this->_testCreateByPut('item');
- $this->_testCreateByPut('search');
- }
-
- private function _testCreateByPut($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $json = API::createUnsavedDataObject($objectType);
- require_once '../../model/ID.inc.php';
- $key = \Zotero_ID::getKey();
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- ]
- );
- $this->assert204($response);
- }
-
-
- public function testSingleObjectDelete() {
- $this->_testSingleObjectDelete('collection');
- $this->_testSingleObjectDelete('item');
- $this->_testSingleObjectDelete('search');
- }
-
-
- public function testMultiObjectDelete() {
- $this->_testMultiObjectDelete('collection');
- $this->_testMultiObjectDelete('item');
- $this->_testMultiObjectDelete('search');
- }
-
-
- public function testDeleted() {
- $self = $this;
-
- API::userClear(self::$config['userID']);
-
- //
- // Create objects
- //
- $objectKeys = array();
- $objectKeys['tag'] = array("foo", "bar");
-
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['collection'][] = API::createCollection("Name", false, $this, 'key');
- $objectKeys['item'][] = API::createItem(
- "book",
- array(
- "title" => "Title",
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $objectKeys['tag'])
- ),
- $this,
- 'key'
- );
- $objectKeys['item'][] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $objectKeys['item'][] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
- $objectKeys['search'][] = API::createSearch("Name", 'default', $this, 'key');
-
- // Get library version
- $response = API::userGet(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'] . "&format=keys&limit=1"
- );
- $libraryVersion1 = $response->getHeader("Last-Modified-Version");
-
- // Delete first object
- $config = self::$config;
- $func = function ($objectType, $libraryVersion) use ($config, $self, $objectKeys) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $response = API::userDelete(
- $config['userID'],
- "$objectTypePlural?key=" . $config['apiKey']
- . "&$keyProp=" . $objectKeys[$objectType][0],
- array("If-Unmodified-Since-Version: " . $libraryVersion)
- );
- $self->assert204($response);
- return $response->getHeader("Last-Modified-Version");
- };
- $tempLibraryVersion = $func('collection', $libraryVersion1);
- $tempLibraryVersion = $func('item', $tempLibraryVersion);
- $tempLibraryVersion = $func('search', $tempLibraryVersion);
- $libraryVersion2 = $tempLibraryVersion;
-
- // Delete second and third objects
- $func = function ($objectType, $libraryVersion) use ($config, $self, $objectKeys) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $response = API::userDelete(
- $config['userID'],
- "$objectTypePlural?key=" . $config['apiKey']
- . "&$keyProp=" . implode(',', array_slice($objectKeys[$objectType], 1)),
- array("If-Unmodified-Since-Version: " . $libraryVersion)
- );
- $self->assert204($response);
- return $response->getHeader("Last-Modified-Version");
- };
- $tempLibraryVersion = $func('collection', $tempLibraryVersion);
- $tempLibraryVersion = $func('item', $tempLibraryVersion);
- $libraryVersion3 = $func('search', $tempLibraryVersion);
-
-
- // Request all deleted objects
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&since=$libraryVersion1"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertNotNull($version);
- $this->assertContentType("application/json", $response);
-
- // Make sure 'newer' is equivalent
- $responseNewer = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion1"
- );
- $this->assertEquals($response->getStatus(), $responseNewer->getStatus());
- $this->assertEquals($response->getBody(), $responseNewer->getBody());
- $this->assertEquals($response->getHeader('Last-Modified-Version'), $responseNewer->getHeader('Last-Modified-Version'));
- $this->assertEquals($response->getHeader('Content-Type'), $responseNewer->getHeader('Content-Type'));
-
- // Verify keys
- $func = function ($json, $objectType, $objectKeys) use ($self) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $self->assertArrayHasKey($objectTypePlural, $json);
- $self->assertCount(sizeOf($objectKeys), $json[$objectTypePlural]);
- foreach ($objectKeys as $key) {
- $self->assertContains($key, $json[$objectTypePlural]);
- }
- };
- $func($json, 'collection', $objectKeys['collection']);
- $func($json, 'item', $objectKeys['item']);
- $func($json, 'search', $objectKeys['search']);
- // Tags aren't deleted by removing from items
- $func($json, 'tag', []);
-
-
- // Request second and third deleted objects
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion2"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertNotNull($version);
- $this->assertContentType("application/json", $response);
-
- // Verify keys
- $func = function ($json, $objectType, $objectKeys) use ($self) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $self->assertArrayHasKey($objectTypePlural, $json);
- $self->assertCount(sizeOf($objectKeys), $json[$objectTypePlural]);
- foreach ($objectKeys as $key) {
- $self->assertContains($key, $json[$objectTypePlural]);
- }
- };
- $func($json, 'collection', array_slice($objectKeys['collection'], 1));
- $func($json, 'item', array_slice($objectKeys['item'], 1));
- $func($json, 'search', array_slice($objectKeys['search'], 1));
- // Tags aren't deleted by removing from items
- $func($json, 'tag', []);
-
-
- // Explicit tag deletion
- $response = API::userDelete(
- self::$config['userID'],
- "tags?key=" . self::$config['apiKey']
- . "&tag=" . implode('%20||%20', $objectKeys['tag']),
- array("If-Unmodified-Since-Version: " . $libraryVersion3)
- );
- $self->assert204($response);
-
- // Verify deleted tags
- $response = API::userGet(
- self::$config['userID'],
- "deleted?key=" . self::$config['apiKey'] . "&newer=$libraryVersion3"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $func($json, 'tag', $objectKeys['tag']);
- }
-
-
- public function testEmptyVersionsResponse() {
- $this->_testEmptyVersionsResponse('collection');
- $this->_testEmptyVersionsResponse('item');
- $this->_testEmptyVersionsResponse('search');
- }
-
-
- private function _testEmptyVersionsResponse($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=versions&$keyProp=NNNNNNNN"
- );
- $this->assert200($response);
- $json = json_decode($response->getBody());
- $this->assertInternalType('object', $json);
- $this->assertCount(0, get_object_vars($json));
- }
-
-
- public function testResponseJSONPost() {
- $this->_testResponseJSONPost('collection');
- $this->_testResponseJSONPost('item');
- $this->_testResponseJSONPost('search');
- }
-
-
- public function testResponseJSONPut() {
- $this->_testResponseJSONPut('collection');
- $this->_testResponseJSONPut('item');
- $this->_testResponseJSONPut('search');
- }
-
-
- public function testPartialWriteFailure() {
- $this->_testPartialWriteFailure('collection');
- $this->_testPartialWriteFailure('item');
- $this->_testPartialWriteFailure('search');
- }
-
-
- public function testPartialWriteFailureWithUnchanged() {
- $this->_testPartialWriteFailureWithUnchanged('collection');
- $this->_testPartialWriteFailureWithUnchanged('item');
- $this->_testPartialWriteFailureWithUnchanged('search');
- }
-
-
- public function testMultiObjectWriteInvalidObject() {
- $this->_testMultiObjectWriteInvalidObject('collection');
- $this->_testMultiObjectWriteInvalidObject('item');
- $this->_testMultiObjectWriteInvalidObject('search');
- }
-
-
- private function _testMultiObjectGet($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
-
- $keys = [];
- switch ($objectType) {
- case 'collection':
- $keys[] = API::createCollection("Name", false, $this, 'key');
- $keys[] = API::createCollection("Name", false, $this, 'key');
- API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $keys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $keys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $keys[] = API::createSearch("Name", 'default', $this, 'key');
- $keys[] = API::createSearch("Name", 'default', $this, 'key');
- API::createSearch("Name", 'default', $this, 'key');
- break;
- }
-
- // HEAD request should include Total-Results
- $response = API::userHead(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $keys)
- );
- $this->assert200($response);
- $this->assertTotalResults(sizeOf($keys), $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $keys)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keys), $response);
-
- // Trailing comma in itemKey parameter
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $keys) . ","
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keys), $response);
- }
-
-
- private function _testSingleObjectDelete($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json = API::createCollection("Name", false, $this, 'json');
- break;
-
- case 'item':
- $json = API::createItem("book", array("title" => "Title"), $this, 'json');
- break;
-
- case 'search':
- $json = API::createSearch("Name", 'default', $this, 'json');
- break;
- }
-
- $objectKey = $json['key'];
- $objectVersion = $json['version'];
-
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert404($response);
- }
-
-
- private function _testMultiObjectDelete($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
-
- $deleteKeys = array();
- $keepKeys = array();
- switch ($objectType) {
- case 'collection':
- $deleteKeys[] = API::createCollection("Name", false, $this, 'key');
- $deleteKeys[] = API::createCollection("Name", false, $this, 'key');
- $keepKeys[] = API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $deleteKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $deleteKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- $keepKeys[] = API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $deleteKeys[] = API::createSearch("Name", 'default', $this, 'key');
- $deleteKeys[] = API::createSearch("Name", 'default', $this, 'key');
- $keepKeys[] = API::createSearch("Name", 'default', $this, 'key');
- break;
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($deleteKeys) + sizeOf($keepKeys), $response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $deleteKeys),
- array(
- "If-Unmodified-Since-Version: " . $libraryVersion
- )
- );
- $this->assert204($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural"
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keepKeys), $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $keepKeys)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($keepKeys), $response);
-
- // Add trailing comma to itemKey param, to test key parsing
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural?$keyProp=" . implode(',', $keepKeys) . ",",
- array(
- "If-Unmodified-Since-Version: " . $libraryVersion
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- private function _testResponseJSONPost($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json1 = ["name" => "Test 1"];
- $json2 = ["name" => "Test 2"];
- break;
-
- case 'item':
- $json1 = API::getItemTemplate('book');
- $json2 = clone $json1;
- $json1->title = "Test 1";
- $json2->title = "Test 2";
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $json1 = ["name" => "Test 1", "conditions" => $conditions];
- $json2 = ["name" => "Test 2", "conditions" => $conditions];
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- $objectTypePlural,
- json_encode([$json1, $json2]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert200ForObject($response, false, 1);
-
- $response = API::userGet(
- self::$config['userID'],
- $objectTypePlural
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- switch ($objectType) {
- case 'item':
- $json[0]['data']['title'] = $json[0]['data']['title'] == "Test 1" ? "Test A" : "Test B";
- $json[1]['data']['title'] = $json[1]['data']['title'] == "Test 2" ? "Test B" : "Test A";
- break;
-
- case 'collection':
- case 'search':
- $json[0]['data']['name'] = $json[0]['data']['name'] == "Test 1" ? "Test A" : "Test B";
- $json[1]['data']['name'] = $json[1]['data']['name'] == "Test 2" ? "Test B" : "Test A";
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- $objectTypePlural,
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert200ForObject($response, false, 1);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- $objectTypePlural
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- switch ($objectTypePlural) {
- case 'item':
- $this->assertEquals("Test A", $json[0]['data']['title']);
- $this->assertEquals("Test B", $json[1]['data']['title']);
- break;
-
- case 'collection':
- case 'search':
- $this->assertEquals("Test A", $json[0]['data']['name']);
- $this->assertEquals("Test B", $json[1]['data']['name']);
- break;
- }
- }
-
-
- private function _testResponseJSONPut($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json1 = ["name" => "Test 1"];
- break;
-
- case 'item':
- $json1 = API::getItemTemplate('book');
- $json1->title = "Test 1";
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $json1 = ["name" => "Test 1", "conditions" => $conditions];
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- $objectTypePlural,
- json_encode([$json1]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert200ForObject($response, false, 0);
- $objectKey = $json['successful'][0]['key'];
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- switch ($objectType) {
- case 'item':
- $json['data']['title'] = "Test 2";
- break;
-
- case 'collection':
- case 'search':
- $json['data']['name'] = "Test 2";
- break;
- }
-
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- switch ($objectTypePlural) {
- case 'item':
- $this->assertEquals("Test 2", $json['data']['title']);
- break;
-
- case 'collection':
- case 'search':
- $this->assertEquals("Test 2", $json['data']['name']);
- break;
- }
- }
-
-
- private function _testPartialWriteFailure($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json1 = array("name" => "Test");
- $json2 = array("name" => str_repeat("1234567890", 6554));
- $json3 = array("name" => "Test");
- break;
-
- case 'item':
- $json1 = API::getItemTemplate('book');
- $json2 = clone $json1;
- $json3 = clone $json1;
- $json2->title = str_repeat("1234567890", 6554);
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $json1 = array("name" => "Test", "conditions" => $conditions);
- $json2 = array("name" => str_repeat("1234567890", 6554), "conditions" => $conditions);
- $json3 = array("name" => "Test", "conditions" => $conditions);
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json1, $json2, $json3]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert413ForObject($response, false, 1);
- $this->assert200ForObject($response, false, 2);
- $successKeys = API::getSuccessfulKeysFromResponse($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- foreach ($successKeys as $key) {
- $this->assertContains($key, $keys);
- }
- }
-
-
- private function _testPartialWriteFailureWithUnchanged($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $json1 = API::createCollection("Test", false, $this, 'jsonData');
- $json2 = array("name" => str_repeat("1234567890", 6554));
- $json3 = array("name" => "Test");
- break;
-
- case 'item':
- $json1 = API::createItem("book", array("title" => "Title"), $this, 'jsonData');
- $json2 = API::getItemTemplate('book');
- $json3 = clone $json2;
- $json2->title = str_repeat("1234567890", 6554);
- break;
-
- case 'search':
- $conditions = array(
- array(
- 'condition' => 'title',
- 'operator' => 'contains',
- 'value' => 'value'
- )
- );
- $json1 = API::createSearch("Name", $conditions, $this, 'jsonData');
- $json2 = array("name" => str_repeat("1234567890", 6554), "conditions" => $conditions);
- $json3 = array("name" => "Test", "conditions" => $conditions);
- break;
- }
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json1, $json2, $json3]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertUnchangedForObject($response, 0);
- $this->assert413ForObject($response, false, 1);
- $this->assert200ForObject($response, false, 2);
- $successKeys = API::getSuccessfulKeysFromResponse($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys&key=" . self::$config['apiKey']
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- foreach ($successKeys as $key) {
- $this->assertContains($key, $keys);
- }
- }
-
-
- private function _testMultiObjectWriteInvalidObject($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode(["foo" => "bar"]),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Uploaded data must be a JSON array");
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([[], ""]),
- array("Content-Type: application/json")
- );
- $this->assert400ForObject($response, "Invalid value for index 0 in uploaded data; expected JSON $objectType object");
- $this->assert400ForObject($response, "Invalid value for index 1 in uploaded data; expected JSON $objectType object", 1);
- }
-}
diff --git a/tests/remote/tests/API/3/ParamsTest.php b/tests/remote/tests/API/3/ParamsTest.php
deleted file mode 100644
index ed6870b4..00000000
--- a/tests/remote/tests/API/3/ParamsTest.php
+++ /dev/null
@@ -1,583 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class ParamsTests extends APITests {
- private static $collectionKeys = array();
- private static $itemKeys = array();
- private static $searchKeys = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testFormatKeys() {
- //
- // Collections
- //
- for ($i=0; $i<5; $i++) {
- self::$collectionKeys[] = API::createCollection("Test", false, null, 'key');
- }
-
- //
- // Items
- //
- for ($i=0; $i<5; $i++) {
- self::$itemKeys[] = API::createItem("book", false, null, 'key');
- }
- self::$itemKeys[] = API::createAttachmentItem("imported_file", [], false, null, 'key');
-
- //
- // Searches
- //
- for ($i=0; $i<5; $i++) {
- self::$searchKeys[] = API::createSearch("Test", 'default', null, 'key');
- }
-
- $this->_testFormatKeys('collection');
- $this->_testFormatKeys('item');
- $this->_testFormatKeys('search');
-
- $this->_testFormatKeysSorted('collection');
- $this->_testFormatKeysSorted('item');
- $this->_testFormatKeysSorted('search');
- }
-
-
- private function _testFormatKeys($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keysVar = $objectType . "Keys";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys"
- );
- $this->assert200($response);
-
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff(self::$$keysVar, $keys), array_diff($keys, self::$$keysVar)
- )
- );
- }
-
-
- private function _testFormatKeysSorted($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keysVar = $objectType . "Keys";
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=keys&order=title"
- );
- $this->assert200($response);
-
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff(self::$$keysVar, $keys), array_diff($keys, self::$$keysVar)
- )
- );
- }
-
-
- public function testObjectKeyParameter() {
- $this->_testObjectKeyParameter('collection');
- $this->_testObjectKeyParameter('item');
- $this->_testObjectKeyParameter('search');
- }
-
-
- private function _testObjectKeyParameter($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $jsonArray = array();
-
- switch ($objectType) {
- case 'collection':
- $jsonArray[] = API::createCollection("Name", false, $this, 'jsonData');
- $jsonArray[] = API::createCollection("Name", false, $this, 'jsonData');
- break;
-
- case 'item':
- $jsonArray[] = API::createItem("book", false, $this, 'jsonData');
- $jsonArray[] = API::createItem("book", false, $this, 'jsonData');
- break;
-
- case 'search':
- $jsonArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'jsonData'
- );
- $jsonArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'jsonData'
- );
- break;
- }
-
- $keys = [];
- foreach ($jsonArray as $json) {
- $keys[] = $json['key'];
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?{$objectType}Key={$keys[0]}"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $this->assertTotalResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?{$objectType}Key={$keys[0]},{$keys[1]}&order={$objectType}KeyList"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $this->assertTotalResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
- $this->assertEquals($keys[1], $json[1]['key']);
- }
-
-
- public function testPagination() {
- self::_testPagination('collection');
- self::_testPagination('group');
- self::_testPagination('item');
- self::_testPagination('search');
- self::_testPagination('tag');
- }
-
-
- private function _createPaginationData($objectType, $num) {
- switch ($objectType) {
- case 'collection':
- for ($i=0; $i<$num; $i++) {
- API::createCollection("Test", false, $this, 'key');
- }
- break;
-
- case 'item':
- for ($i=0; $i<$num; $i++) {
- API::createItem("book", false, $this, 'key');
- }
- break;
-
- case 'search':
- for ($i=0; $i<$num; $i++) {
- API::createSearch("Test", 'default', $this, 'key');
- }
- break;
-
- case 'tag':
- API::createItem("book", [
- 'tags' => [
- 'a',
- 'b'
- ]
- ], $this);
- API::createItem("book", [
- 'tags' => [
- 'c',
- 'd',
- 'e'
- ]
- ], $this);
- break;
- }
- }
-
-
- private function _testPagination($objectType) {
- API::userClear(self::$config['userID']);
-
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $limit = 2;
- $totalResults = 5;
- $formats = ['json', 'atom', 'keys'];
-
- // Create sample data
- switch ($objectType) {
- case 'collection':
- case 'item':
- case 'search':
- case 'tag':
- $this->_createPaginationData($objectType, $totalResults);
- break;
- }
-
- switch ($objectType) {
- case 'item':
- array_push($formats, 'bibtex');
- break;
-
- case 'tag':
- $formats = array_filter($formats, function ($val) { return !in_array($val, ['keys']); });
- break;
-
- case 'group':
- // Change if the config changes
- $limit = 1;
- $totalResults = self::$config['numOwnedGroups'];
- $formats = array_filter($formats, function ($val) { return !in_array($val, ['keys']); });
- break;
- }
-
- foreach ($formats as $format) {
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?limit=$limit&format=$format"
- );
- $this->assert200($response);
- $this->assertNumResults($limit, $response);
- $this->assertTotalResults($totalResults, $response);
- $links = $this->parseLinkHeader($response->getHeader('Link'));
- $this->assertArrayNotHasKey('first', $links);
- $this->assertArrayNotHasKey('prev', $links);
- $this->assertArrayHasKey('next', $links);
- $this->assertEquals($limit, $links['next']['params']['start']);
- $this->assertEquals($limit, $links['next']['params']['limit']);
- $this->assertArrayHasKey('last', $links);
- $lastStart = $totalResults - ($totalResults % $limit);
- if ($lastStart == $totalResults) {
- $lastStart -= $limit;
- }
- $this->assertEquals($lastStart, $links['last']['params']['start']);
- $this->assertEquals($limit, $links['last']['params']['limit']);
-
- // TODO: Test with more groups
- if ($objectType == 'group') continue;
-
- // Start at 1
- $start = 1;
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?start=$start&limit=$limit&format=$format"
- );
- $this->assert200($response);
- $this->assertNumResults($limit, $response);
- $this->assertTotalResults($totalResults, $response);
- $links = $this->parseLinkHeader($response->getHeader('Link'));
- $this->assertArrayHasKey('first', $links);
- $this->assertArrayNotHasKey('start', $links['first']['params']);
- $this->assertEquals($limit, $links['first']['params']['limit']);
- $this->assertArrayHasKey('prev', $links);
- $this->assertArrayNotHasKey('start', $links['prev']['params']);
- $this->assertEquals($limit, $links['prev']['params']['limit']);
- $this->assertArrayHasKey('next', $links);
- $this->assertEquals($start + $limit, $links['next']['params']['start']);
- $this->assertEquals($limit, $links['next']['params']['limit']);
- $this->assertArrayHasKey('last', $links);
- $lastStart = $totalResults - ($totalResults % $limit);
- if ($lastStart == $totalResults) {
- $lastStart -= $limit;
- }
- $this->assertEquals($lastStart, $links['last']['params']['start']);
- $this->assertEquals($limit, $links['last']['params']['limit']);
-
- // Start at 2
- $start = 2;
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?start=$start&limit=$limit&format=$format"
- );
- $this->assert200($response);
- $this->assertNumResults($limit, $response);
- $this->assertTotalResults($totalResults, $response);
- $links = $this->parseLinkHeader($response->getHeader('Link'));
- $this->assertArrayHasKey('first', $links);
- $this->assertArrayNotHasKey('start', $links['first']['params']);
- $this->assertEquals($limit, $links['first']['params']['limit']);
- $this->assertArrayHasKey('prev', $links);
- $this->assertArrayNotHasKey('start', $links['prev']['params']);
- $this->assertEquals($limit, $links['prev']['params']['limit']);
- $this->assertArrayHasKey('next', $links);
- $this->assertEquals($start + $limit, $links['next']['params']['start']);
- $this->assertEquals($limit, $links['next']['params']['limit']);
- $this->assertArrayHasKey('last', $links);
- $lastStart = $totalResults - ($totalResults % $limit);
- if ($lastStart == $totalResults) {
- $lastStart -= $limit;
- }
- $this->assertEquals($lastStart, $links['last']['params']['start']);
- $this->assertEquals($limit, $links['last']['params']['limit']);
-
- // Start at 3
- $start = 3;
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?start=$start&limit=$limit&format=$format"
- );
- $this->assert200($response);
- $this->assertNumResults($limit, $response);
- $this->assertTotalResults($totalResults, $response);
- $links = $this->parseLinkHeader($response->getHeader('Link'));
- $this->assertArrayHasKey('first', $links);
- $this->assertArrayNotHasKey('start', $links['first']['params']);
- $this->assertEquals($limit, $links['first']['params']['limit']);
- $this->assertArrayHasKey('prev', $links);
- $this->assertEquals(max(0, $start - $limit), $links['prev']['params']['start']);
- $this->assertEquals($limit, $links['prev']['params']['limit']);
- $this->assertArrayNotHasKey('next', $links);
- $this->assertArrayHasKey('last', $links);
- $lastStart = $totalResults - ($totalResults % $limit);
- if ($lastStart == $totalResults) {
- $lastStart -= $limit;
- }
- $this->assertEquals($lastStart, $links['last']['params']['start']);
- $this->assertEquals($limit, $links['last']['params']['limit']);
- }
- }
-
-
- // Test disabled because it's slow
- /*public function testPaginationWithItemKey() {
- $totalResults = 27;
-
- for ($i=0; $i<$totalResults; $i++) {
- API::createItem("book", false, $this, 'key');
- }
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&limit=50"
- );
- $keys = explode("\n", trim($response->getBody()));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json&itemKey=" . join(",", $keys)
- );
- $json = API::getJSONFromResponse($response);;
- $this->assertCount($totalResults, $json);
- }*/
-
-
- public function testCollectionQuickSearch() {
- $title1 = "Test Title";
- $title2 = "Another Title";
-
- $keys = [];
- $keys[] = API::createCollection($title1, [], $this, 'key');
- $keys[] = API::createCollection($title2, [], $this, 'key');
-
- // Search by title
- $response = API::userGet(
- self::$config['userID'],
- "collections?q=another"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[1], $json[0]['key']);
-
- // No results
- $response = API::userGet(
- self::$config['userID'],
- "collections?q=nothing"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- public function testItemQuickSearch() {
- $title1 = "Test Title";
- $title2 = "Another Title";
- $year2 = "2013";
-
- $keys = [];
- $keys[] = API::createItem("book", [
- 'title' => $title1
- ], $this, 'key');
- $keys[] = API::createItem("journalArticle", [
- 'title' => $title2,
- 'date' => "November 25, $year2"
- ], $this, 'key');
-
- // Search by title
- $response = API::userGet(
- self::$config['userID'],
- "items?q=" . urlencode($title1)
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
-
- // TODO: Search by creator
-
- // Search by year
- $response = API::userGet(
- self::$config['userID'],
- "items?q=$year2"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[1], $json[0]['key']);
-
- // Search by year + 1
- $response = API::userGet(
- self::$config['userID'],
- "items?q=" . ($year2 + 1)
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
- }
-
-
- public function testItemQuickSearchOrderByDate() {
- $title1 = "Test Title";
- $title2 = "Another Title";
-
- $keys = [];
- $keys[] = API::createItem("book", [
- 'title' => $title1,
- 'date' => "February 12, 2013"
- ], $this, 'key');
- $keys[] = API::createItem("journalArticle", [
- 'title' => $title2,
- 'date' => "November 25, 2012"
- ], $this, 'key');
-
- // Search for one by title
- $response = API::userGet(
- self::$config['userID'],
- "items?q=" . urlencode($title1)
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
-
- // Search by both by title, date asc
- $response = API::userGet(
- self::$config['userID'],
- "items?q=title&sort=date&direction=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[1], $json[0]['key']);
- $this->assertEquals($keys[0], $json[1]['key']);
-
- // Search by both by title, date asc, with old-style parameters
- $response = API::userGet(
- self::$config['userID'],
- "items?q=title&order=date&sort=asc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[1], $json[0]['key']);
- $this->assertEquals($keys[0], $json[1]['key']);
-
- // Search by both by title, date desc
- $response = API::userGet(
- self::$config['userID'],
- "items?q=title&sort=date&direction=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
- $this->assertEquals($keys[1], $json[1]['key']);
-
- // Search by both by title, date desc, with old-style parameters
- $response = API::userGet(
- self::$config['userID'],
- "items?q=title&order=date&sort=desc"
- );
- $this->assert200($response);
- $this->assertNumResults(2, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($keys[0], $json[0]['key']);
- $this->assertEquals($keys[1], $json[1]['key']);
- }
-
-
- private function parseLinkHeader($links) {
- $this->assertNotNull($links);
- $links = explode(',', $links);
- $parsedLinks = [];
- foreach ($links as $link) {
- list($uri, $rel) = explode('; ', trim($link));
- $this->assertRegExp('/^$/', $uri);
- $this->assertRegExp('/^rel="[a-z]+"$/', $rel);
- $uri = substr($uri, 1, -1);
- $rel = substr($rel, strlen('rel="'), -1);
-
- parse_str(parse_url($uri, PHP_URL_QUERY), $params);
- $parsedLinks[$rel] = [
- 'uri' => $uri,
- 'params' => $params
- ];
- }
- return $parsedLinks;
- }
-}
diff --git a/tests/remote/tests/API/3/PermissionsTest.php b/tests/remote/tests/API/3/PermissionsTest.php
deleted file mode 100644
index 0adf8a02..00000000
--- a/tests/remote/tests/API/3/PermissionsTest.php
+++ /dev/null
@@ -1,425 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class PermissionsTest extends APITests {
- private static $publicGroupID;
-
- public function setUp() {
- parent::setUp();
- API::resetKey(self::$config['apiKey']);
- API::setKeyUserPermission(self::$config['apiKey'], 'library', true);
- API::setKeyUserPermission(self::$config['apiKey'], 'write', true);
- API::setKeyGroupPermission(self::$config['apiKey'], 0, 'write', true);
- }
-
-
- public function testUserGroupsAnonymousJSON() {
- API::useAPIKey(false);
- $response = API::get("users/" . self::$config['userID'] . "/groups");
- $this->assert200($response);
-
- $this->assertTotalResults(self::$config['numPublicGroups'], $response);
-
- // Make sure they're the right groups
- $json = API::getJSONFromResponse($response);
- $groupIDs = array_map(function ($data) {
- return $data['id'];
- }, $json);
- $this->assertContains(self::$config['ownedPublicGroupID'], $groupIDs);
- $this->assertContains(self::$config['ownedPublicNoAnonymousGroupID'], $groupIDs);
- }
-
-
- public function testUserGroupsAnonymousAtom() {
- API::useAPIKey(false);
- $response = API::get("users/" . self::$config['userID'] . "/groups?content=json");
- $this->assert200($response);
-
- $this->assertTotalResults(self::$config['numPublicGroups'], $response);
-
- // Make sure they're the right groups
- $xml = API::getXMLFromResponse($response);
- $groupIDs = array_map(function ($id) {
- return (int) $id;
- }, $xml->xpath('//atom:entry/zapi:groupID'));
- $this->assertContains(self::$config['ownedPublicGroupID'], $groupIDs);
- $this->assertContains(self::$config['ownedPublicNoAnonymousGroupID'], $groupIDs);
- }
-
-
- public function testUserGroupsOwned() {
- API::useAPIKey(self::$config['apiKey']);
- $response = API::userGet(self::$config['userID'], "groups");
- $this->assert200($response);
-
- $this->assertNumResults(self::$config['numOwnedGroups'], $response);
- $this->assertTotalResults(self::$config['numOwnedGroups'], $response);
- }
-
-
- public function test_should_see_private_group_listed_when_using_key_with_library_read_access() {
- API::resetKey(self::$config['apiKey']);
- $response = API::userGet(self::$config['userID'], "groups");
- $this->assert200($response);
-
- $this->assertNumResults(self::$config['numPublicGroups'], $response);
-
- // Grant key read permission to library
- API::setKeyGroupPermission(
- self::$config['apiKey'],
- self::$config['ownedPrivateGroupID'],
- 'library',
- true
- );
-
- $response = API::userGet(self::$config['userID'], "groups");
- $this->assertNumResults(self::$config['numOwnedGroups'], $response);
- $this->assertTotalResults(self::$config['numOwnedGroups'], $response);
-
- $json = API::getJSONFromResponse($response);
- $groupIDs = array_map(function ($data) {
- return $data['id'];
- }, $json);
- $this->assertContains(self::$config['ownedPrivateGroupID'], $groupIDs);
- }
-
-
- public function testGroupLibraryReading() {
- $groupID = self::$config['ownedPublicNoAnonymousGroupID'];
- API::groupClear($groupID);
-
- $json = API::groupCreateItem(
- $groupID,
- 'book',
- [
- 'title' => "Test"
- ],
- $this
- );
-
- try {
- API::useAPIKey(self::$config['apiKey']);
- $response = API::groupGet($groupID, "items");
- $this->assert200($response);
- $this->assertNumResults(1, $response);
-
- // An anonymous request should fail, because libraryReading is members
- API::useAPIKey(false);
- $response = API::groupGet($groupID, "items");
- $this->assert403($response);
- }
- finally {
- API::groupClear($groupID);
- }
- }
-
-
- public function test_shouldnt_be_able_to_write_to_group_using_key_with_library_read_access() {
- API::resetKey(self::$config['apiKey']);
-
- // Grant key read (not write) permission to library
- API::setKeyGroupPermission(
- self::$config['apiKey'],
- self::$config['ownedPrivateGroupID'],
- 'library',
- true
- );
-
- $response = API::get("items/new?itemType=book");
- $json = json_decode($response->getBody(), true);
-
- $response = API::groupPost(
- self::$config['ownedPrivateGroupID'],
- "items",
- json_encode([
- "items" => [$json]
- ]),
- ["Content-Type: application/json"]
- );
- $this->assert403($response);
- }
-
-
- /**
- * A key without note access shouldn't be able to create a note
- */
- /*public function testKeyNoteAccessWriteError() {
- API::setKeyUserPermission(self::$config['apiKey'], 'notes', false);
-
- $response = API::get("items/new?itemType=note");
- $json = json_decode($response->getBody());
- $json->note = "Test";
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode(array(
- "items" => array($json)
- )),
- array("Content-Type: application/json")
- );
- $this->assert403($response);
- }*/
-
-
- public function testKeyNoteAccess() {
- API::userClear(self::$config['userID']);
-
- API::setKeyUserPermission(self::$config['apiKey'], 'notes', true);
-
- $keys = array();
- $topLevelKeys = array();
- $bookKeys = array();
-
- $key = API::createItem('book', array("title" => "A"), $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
- $bookKeys[] = $key;
-
- $key = API::createNoteItem("B", false, $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
-
- $key = API::createNoteItem("C", false, $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
-
- $key = API::createNoteItem("D", false, $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
-
- $key = API::createNoteItem("E", false, $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
-
- $key = API::createItem('book', array("title" => "F"), $this, 'key');
- $keys[] = $key;
- $topKeys[] = $key;
- $bookKeys[] = $key;
-
- $key = API::createNoteItem("G", $key, $this, 'key');
- $keys[] = $key;
-
- // Create collection and add items to it
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([
- [
- "name" => "Test",
- "parentCollection" => false
- ]
- ]),
- array("Content-Type: application/json")
- );
- $this->assert200ForObject($response);
- $collectionKey = API::getFirstSuccessKeyFromResponse($response);
-
- $response = API::userPost(
- self::$config['userID'],
- "collections/$collectionKey/items",
- implode(" ", $topKeys)
- );
- $this->assert204($response);
-
- //
- // format=atom
- //
- // Root
- $response = API::userGet(
- self::$config['userID'], "items"
- );
- $this->assertNumResults(sizeOf($keys), $response);
- $this->assertTotalResults(sizeOf($keys), $response);
-
- // Top
- $response = API::userGet(
- self::$config['userID'], "items/top"
- );
- $this->assertNumResults(sizeOf($topKeys), $response);
- $this->assertTotalResults(sizeOf($topKeys), $response);
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top"
- );
- $this->assertNumResults(sizeOf($topKeys), $response);
- $this->assertTotalResults(sizeOf($topKeys), $response);
-
- //
- // format=keys
- //
- // Root
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($keys), explode("\n", trim($response->getBody())));
-
- // Top
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody())));
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items/top?format=keys"
- );
- $this->assert200($response);
- $this->assertCount(sizeOf($topKeys), explode("\n", trim($response->getBody())));
-
- // Remove notes privilege from key
- API::setKeyUserPermission(self::$config['apiKey'], 'notes', false);
-
- //
- // format=json
- //
- // totalResults with limit
- $response = API::userGet(
- self::$config['userID'],
- "items?limit=1"
- );
- $this->assertNumResults(1, $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // And without limit
- $response = API::userGet(
- self::$config['userID'],
- "items"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Top
- $response = API::userGet(
- self::$config['userID'],
- "items/top"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- //
- // format=atom
- //
- // totalResults with limit
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom&limit=1"
- );
- $this->assertNumResults(1, $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // And without limit
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Top
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=atom"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- // Collection
- $response = API::userGet(
- self::$config['userID'],
- "collections/$collectionKey/items?format=atom"
- );
- $this->assertNumResults(sizeOf($bookKeys), $response);
- $this->assertTotalResults(sizeOf($bookKeys), $response);
-
- //
- // format=keys
- //
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys"
- );
- $keys = explode("\n", trim($response->getBody()));
- sort($keys);
- $this->assertEmpty(
- array_merge(
- array_diff($bookKeys, $keys), array_diff($keys, $bookKeys)
- )
- );
- }
-
-
- public function testTagDeletePermissions() {
- API::userClear(self::$config['userID']);
-
- API::createItem('book', array(
- "tags" => array(
- array(
- "tag" => "A"
- )
- )
- ), $this);
-
- $libraryVersion = API::getLibraryVersion();
-
- API::setKeyUserPermission(self::$config['apiKey'], 'write', false);
-
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=A&key=" . self::$config['apiKey']
- );
- $this->assert403($response);
-
- API::setKeyUserPermission(self::$config['apiKey'], 'write', true);
-
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=A&key=" . self::$config['apiKey'],
- array("If-Unmodified-Since-Version: $libraryVersion")
- );
- $this->assert204($response);
- }
-}
diff --git a/tests/remote/tests/API/3/PublicationsTest.php b/tests/remote/tests/API/3/PublicationsTest.php
deleted file mode 100644
index f16283a3..00000000
--- a/tests/remote/tests/API/3/PublicationsTest.php
+++ /dev/null
@@ -1,927 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API, HTTP, Z_Tests;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-/**
- * @group s3
- */
-class PublicationsTests extends APITests {
- private static $toDelete = [];
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- $s3Client = Z_Tests::$AWS->createS3();
- foreach (self::$toDelete as $file) {
- try {
- $s3Client->deleteObject([
- 'Bucket' => self::$config['s3Bucket'],
- 'Key' => $file
- ]);
- }
- catch (\Aws\S3\Exception\S3Exception $e) {
- if ($e->getAwsErrorCode() == 'NoSuchKey') {
- echo "\n$file not found on S3 to delete\n";
- }
- else {
- throw $e;
- }
- }
- }
-
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
-
- API::userClear(self::$config['userID']);
-
- // Default to anonymous requests
- API::useAPIKey("");
- }
-
-
- //
- // Test read requests for empty publications list
- //
- public function test_should_return_no_results_for_empty_publications_list() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/items");
- $this->assert200($response);
- $this->assertNoResults($response);
- $this->assertInternalType("numeric", $response->getHeader('Last-Modified-Version'));
- }
-
-
- public function test_should_return_no_results_for_empty_publications_list_with_key() {
- API::useAPIKey(self::$config['apiKey']);
- $response = API::get("users/" . self::$config['userID'] . "/publications/items");
- $this->assert200($response);
- $this->assertNoResults($response);
- $this->assertInternalType("numeric", $response->getHeader('Last-Modified-Version'));
- }
-
-
- public function test_should_return_no_atom_results_for_empty_publications_list() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/items?format=atom");
- $this->assert200($response);
- $this->assertNoResults($response);
- $this->assertInternalType("numeric", $response->getHeader('Last-Modified-Version'));
- }
-
-
- // Disabled until it works
- /*public function test_should_return_304_for_request_with_etag() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/items");
- $this->assert200($response);
- $etag = $response->getHeader("ETag");
- $this->assertNotNull($etag);
-
- // Repeat request with ETag
- $response = API::get(
- "users/" . self::$config['userID'] . "/publications/items",
- [
- "If-None-Match: $etag"
- ]
- );
- $this->assert304($response);
- $this->assertEquals($etag, $response->getHeader("ETag"));
- }*/
-
-
- // Disabled until after integrated My Publications upgrade
- /*public function test_should_return_404_for_settings_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/settings");
- $this->assert404($response);
- }*/
- public function test_should_return_200_for_settings_request_with_no_items() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/settings");
- $this->assert200($response);
- $this->assertNoResults($response);
- }
- public function test_should_return_400_for_settings_request_with_items() {
- API::useAPIKey(self::$config['apiKey']);
- $response = API::createItem("book", ['inPublications' => true], $this, 'response');
- $this->assert200ForObject($response);
-
- $response = API::get("users/" . self::$config['userID'] . "/publications/settings");
- $this->assert400($response);
- }
-
- // Disabled until after integrated My Publications upgrade
- /*public function test_should_return_404_for_deleted_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/deleted?since=0");
- $this->assert404($response);
- }*/
- public function test_should_return_200_for_deleted_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/deleted?since=0");
- $this->assert200($response);
- }
-
- public function test_should_return_404_for_collections_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/collections");
- $this->assert404($response);
- }
-
-
- public function test_should_return_404_for_searches_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/searches");
- $this->assert404($response);
- }
-
-
- public function test_should_return_403_for_anonymous_write() {
- $json = API::getItemTemplate("book");
- $response = API::userPost(
- self::$config['userID'],
- "publications/items",
- json_encode($json)
- );
- $this->assert403($response);
- }
-
-
- public function test_should_return_405_for_authenticated_write() {
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("book");
- $response = API::userPost(
- self::$config['userID'],
- "publications/items",
- json_encode($json)
- );
- $this->assert405($response);
- }
-
-
- public function test_should_return_404_for_anonymous_request_for_item_not_in_publications() {
- // Create item
- API::useAPIKey(self::$config['apiKey']);
- $key = API::createItem("book", [], $this, 'key');
-
- // Fetch anonymously
- API::useAPIKey();
- $response = API::get("users/" . self::$config['userID'] . "/publications/items/$key");
- $this->assert404($response);
- }
-
-
- public function test_should_return_404_for_authenticated_request_for_item_not_in_publications() {
- // Create item
- API::useAPIKey(self::$config['apiKey']);
- $key = API::createItem("book", [], $this, 'key');
-
- // Fetch anonymously
- $response = API::get("users/" . self::$config['userID'] . "/publications/items/$key");
- $this->assert404($response);
- }
-
-
- public function test_should_trigger_notification_on_publications_topic() {
- // Create item
- API::useAPIKey(self::$config['apiKey']);
- $response = API::createItem("book", ['inPublications' => true], $this, 'response');
- $version = API::getJSONFromResponse($response)['successful'][0]['version'];
-
- // Test notification for publications topic (in addition to regular library)
- $this->assertCountNotifications(2, $response);
- $this->assertHasNotification([
- 'event' => 'topicUpdated',
- 'topic' => '/users/' . self::$config['userID'],
- 'version' => $version
- ], $response);
- $this->assertHasNotification([
- 'event' => 'topicUpdated',
- 'topic' => '/users/' . self::$config['userID'] . '/publications'
- ], $response);
-
- $json = API::getJSONFromResponse($response);
- }
-
-
- public function test_should_show_item_for_anonymous_single_object_request() {
- // Create item
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- // Read item anonymously
- API::useAPIKey("");
-
- // JSON
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$itemKey"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(self::$config['username'], $json['library']['name']);
- $this->assertEquals("user", $json['library']['type']);
-
- // Atom
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$itemKey?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(self::$config['username'], (string) $xml->author->name);
- }
-
-
- public function test_should_show_item_for_anonymous_multi_object_request() {
- // Create item
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- // Read item anonymously
- API::useAPIKey("");
-
- // JSON
- $response = API::userGet(
- self::$config['userID'],
- "publications/items"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertContains($itemKey, array_map(function ($item) {
- return $item['key'];
- }, $json));
-
- // Atom
- $response = API::userGet(
- self::$config['userID'],
- "publications/items?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $xpath = $xml->xpath('//atom:entry/zapi:key');
- $this->assertContains($itemKey, $xpath);
- }
-
-
- public function test_shouldnt_show_child_item_not_in_publications() {
- // Create parent item
- API::useAPIKey(self::$config['apiKey']);
- $parentItemKey = API::createItem("book", ['title' => 'A', 'inPublications' => true], $this, 'key');
-
- // Create shown child attachment
- $json1 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json1->title = 'B';
- $json1->parentItem = $parentItemKey;
- $json1->inPublications = true;
- // Create hidden child attachment
- $json2 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json2->title = 'C';
- $json2->parentItem = $parentItemKey;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json1, $json2])
- );
- $this->assert200($response);
-
- // Anonymous read
- API::useAPIKey("");
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json);
- $titles = array_map(function ($item) {
- return $item['data']['title'];
- }, $json);
- $this->assertContains('A', $titles);
- $this->assertContains('B', $titles);
- $this->assertNotContains('C', $titles);
- }
-
-
- public function test_shouldnt_show_child_item_not_in_publications_for_item_children_request() {
- // Create parent item
- API::useAPIKey(self::$config['apiKey']);
- $parentItemKey = API::createItem("book", ['title' => 'A', 'inPublications' => true], $this, 'key');
-
- // Create shown child attachment
- $json1 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json1->title = 'B';
- $json1->parentItem = $parentItemKey;
- $json1->inPublications = true;
- // Create hidden child attachment
- $json2 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json2->title = 'C';
- $json2->parentItem = $parentItemKey;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json1, $json2])
- );
- $this->assert200($response);
-
- // Anonymous read
- API::useAPIKey("");
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$parentItemKey/children"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $titles = array_map(function ($item) {
- return $item['data']['title'];
- }, $json);
- $this->assertContains('B', $titles);
- }
-
-
- public function test_shouldnt_include_hidden_child_items_in_numChildren() {
- // Create parent item
- API::useAPIKey(self::$config['apiKey']);
- $parentItemKey = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- // Create shown child attachment
- $json1 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json1->title = 'A';
- $json1->parentItem = $parentItemKey;
- $json1->inPublications = true;
- // Create shown child note
- $json2 = API::getItemTemplate("note");
- $json2->note = 'B';
- $json2->parentItem = $parentItemKey;
- $json2->inPublications = true;
- // Create hidden child attachment
- $json3 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json3->title = 'C';
- $json3->parentItem = $parentItemKey;
- // Create deleted child attachment
- $json4 = API::getItemTemplate("note");
- $json4->note = 'D';
- $json4->parentItem = $parentItemKey;
- $json4->inPublications = true;
- $json4->deleted = true;
- // Create hidden deleted child attachment
- $json5 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json5->title = 'E';
- $json5->parentItem = $parentItemKey;
- $json5->deleted = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json1, $json2, $json3, $json4, $json5])
- );
- $this->assert200($response);
-
- // Anonymous read
- API::useAPIKey("");
-
- // JSON
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$parentItemKey"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(2, $json['meta']['numChildren']);
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$parentItemKey/children"
- );
-
- // Atom
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$parentItemKey?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(2, (int) array_get_first($xml->xpath('/atom:entry/zapi:numChildren')));
- }
-
-
- public function test_should_include_download_details() {
- $file = "work/file";
- $fileContents = self::getRandomUnicodeString();
- $contentType = "text/html";
- $charset = "utf-8";
- file_put_contents($file, $fileContents);
- $hash = md5_file($file);
- $filename = "test_" . $fileContents;
- $mtime = filemtime($file) * 1000;
- $size = filesize($file);
-
- $parentItemKey = API::createItem("book", ['title' => 'A', 'inPublications' => true], $this, 'key');
- $json = API::createAttachmentItem("imported_file", [
- 'parentItem' => $parentItemKey,
- 'inPublications' => true,
- 'contentType' => $contentType,
- 'charset' => $charset
- ], false, $this, 'jsonData');
- $key = $json['key'];
- $originalVersion = $json['version'];
-
- //
- // Get upload authorization
- //
- API::useAPIKey(self::$config['apiKey']);
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- $this->implodeParams([
- "md5" => $hash,
- "mtime" => $mtime,
- "filename" => $filename,
- "filesize" => $size
- ]),
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- self::$toDelete[] = "$hash";
-
- //
- // Upload to S3
- //
- $response = HTTP::post(
- $json['url'],
- $json['prefix'] . $fileContents . $json['suffix'],
- [
- "Content-Type: {$json['contentType']}"
- ]
- );
- $this->assert201($response);
-
- //
- // Register upload
- //
- $response = API::userPost(
- self::$config['userID'],
- "items/$key/file",
- "upload=" . $json['uploadKey'],
- [
- "Content-Type: application/x-www-form-urlencoded",
- "If-None-Match: *"
- ]
- );
- $this->assert204($response);
- $newVersion = $response->getHeader('Last-Modified-Version');
- $this->assertGreaterThan($originalVersion, $newVersion);
-
- // Anonymous read
- API::useAPIKey("");
-
- // Verify attachment item metadata (JSON)
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$key"
- );
- $json = API::getJSONFromResponse($response);
- $jsonData = $json['data'];
- $this->assertEquals($hash, $jsonData['md5']);
- $this->assertEquals($mtime, $jsonData['mtime']);
- $this->assertEquals($filename, $jsonData['filename']);
- $this->assertEquals($contentType, $jsonData['contentType']);
- $this->assertEquals($charset, $jsonData['charset']);
-
- // Verify download details (JSON)
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items/$key/file/view%",
- $json['links']['enclosure']['href']
- );
-
- // Verify attachment item metadata (Atom)
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$key?format=atom"
- );
- $xml = API::getXMLFromResponse($response);
- $href = (string) array_get_first($xml->xpath('//atom:entry/atom:link[@rel="enclosure"]'))['href'];
-
- // Verify download details (JSON)
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items/$key/file/view%",
- $href
- );
-
- // Check access to file
- preg_match(
- "%https?://[^/]+/(users/" . self::$config['userID'] . "/publications/items/$key/file/view)%",
- $href,
- $matches
- );
- $fileURL = $matches[1];
- $response = API::get($fileURL);
- $this->assert302($response);
-
- // Remove item from My Publications
- API::useAPIKey(self::$config['apiKey']);
-
- $json['data']['inPublications'] = false;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- [
- "Content-Type" => "application/json"
- ]
- );
- $this->assert200ForObject($response);
-
- // No more access via publications URL
- API::useAPIKey();
- $response = API::get($fileURL);
- $this->assert404($response);
- }
-
-
- public function test_shouldnt_show_child_items_in_top_mode() {
- // Create parent item
- API::useAPIKey(self::$config['apiKey']);
- $parentItemKey = API::createItem("book", ['title' => 'A', 'inPublications' => true], $this, 'key');
-
- // Create shown child attachment
- $json1 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json1->title = 'B';
- $json1->parentItem = $parentItemKey;
- $json1->inPublications = true;
- // Create hidden child attachment
- $json2 = API::getItemTemplate("attachment&linkMode=imported_file");
- $json2->title = 'C';
- $json2->parentItem = $parentItemKey;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json1, $json2])
- );
- $this->assert200($response);
-
- // Anonymous read
- API::useAPIKey("");
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/top"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json);
- $titles = array_map(function ($item) {
- return $item['data']['title'];
- }, $json);
- $this->assertContains('A', $titles);
- }
-
-
- public function test_shouldnt_show_trashed_item() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", ['inPublications' => true, 'deleted' => true], $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$itemKey"
- );
- $this->assert404($response);
- }
-
-
- public function test_shouldnt_show_restricted_properties() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", [ 'inPublications' => true ], $this, 'key');
-
- // JSON
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$itemKey"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayNotHasKey('inPublications', $json['data']);
- $this->assertArrayNotHasKey('collections', $json['data']);
- $this->assertArrayNotHasKey('relations', $json['data']);
- $this->assertArrayNotHasKey('tags', $json['data']);
- $this->assertArrayNotHasKey('dateAdded', $json['data']);
- $this->assertArrayNotHasKey('dateModified', $json['data']);
-
- // Atom
- $response = API::userGet(
- self::$config['userID'],
- "publications/items/$itemKey?format=atom&content=html,json"
- );
- $this->assert200($response);
-
- // HTML in Atom
- $html = API::getContentFromAtomResponse($response, 'html');
- $this->assertCount(0, $html->xpath('//html:tr[@class="publications"]'));
-
- // JSON in Atom
- $json = API::getContentFromAtomResponse($response, 'json');
- $this->assertArrayNotHasKey('inPublications', $json);
- $this->assertArrayNotHasKey('collections', $json);
- $this->assertArrayNotHasKey('relations', $json);
- $this->assertArrayNotHasKey('tags', $json);
- $this->assertArrayNotHasKey('dateAdded', $json);
- $this->assertArrayNotHasKey('dateModified', $json);
- }
-
-
- public function test_shouldnt_show_trashed_item_in_versions_response() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey1 = API::createItem("book", ['inPublications' => true], $this, 'key');
- $itemKey2 = API::createItem("book", ['inPublications' => true, 'deleted' => true], $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "publications/items?format=versions"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey($itemKey1, $json);
- $this->assertArrayNotHasKey($itemKey2, $json);
-
- // Shouldn't show with includeTrashed=1 here
- $response = API::userGet(
- self::$config['userID'],
- "publications/items?format=versions&includeTrashed=1"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayHasKey($itemKey1, $json);
- $this->assertArrayNotHasKey($itemKey2, $json);
- }
-
-
- public function test_should_show_publications_urls_in_json_response_for_single_object_request() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- $response = API::get("users/" . self::$config['userID'] . "/publications/items/$itemKey");
- $json = API::getJSONFromResponse($response);
-
- // rel="self"
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items/$itemKey%",
- $json['links']['self']['href']
- );
- }
-
-
- public function test_should_show_publications_urls_in_json_response_for_multi_object_request() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey1 = API::createItem("book", ['inPublications' => true], $this, 'key');
- $itemKey2 = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- $response = API::get("users/" . self::$config['userID'] . "/publications/items?limit=1");
- $json = API::getJSONFromResponse($response);
-
- // Parse Link header
- $links = API::parseLinkHeader($response);
-
- // Entry rel="self"
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items/($itemKey1|$itemKey2)%",
- $json[0]['links']['self']['href']
- );
-
- // rel="next"
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items%",
- $links['next']
- );
-
- // TODO: rel="alternate" (what should this be?)
- }
-
-
- public function test_should_show_publications_urls_in_atom_response_for_single_object_request() {
- API::useAPIKey(self::$config['apiKey']);
- $itemKey = API::createItem("book", ['inPublications' => true], $this, 'key');
-
- $response = API::get("users/" . self::$config['userID'] . "/publications/items/$itemKey?format=atom");
- $xml = API::getXMLFromResponse($response);
-
- // id
- $this->assertRegExp(
- "%http://[^/]+/users/" . self::$config['userID'] . "/items/$itemKey%",
- (string) ($xml->xpath('//atom:id')[0])
- );
-
- // rel="self"
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items/$itemKey\?format=atom%",
- (string) ($xml->xpath('//atom:link[@rel="self"]')[0]['href'])
- );
-
- // TODO: rel="alternate"
- }
-
- public function test_should_show_publications_urls_in_atom_response_for_multi_object_request() {
- $response = API::get("users/" . self::$config['userID'] . "/publications/items?format=atom");
- $xml = API::getXMLFromResponse($response);
-
- // id
- $this->assertRegExp(
- "%http://[^/]+/users/" . self::$config['userID'] . "/publications/items%",
- (string) ($xml->xpath('//atom:id')[0])
- );
-
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items\?format=atom%",
- (string) ($xml->xpath('//atom:link[@rel="self"]')[0]['href'])
- );
-
- // rel="first"
- $this->assertRegExp(
- "%https?://[^/]+/users/" . self::$config['userID'] . "/publications/items\?format=atom%",
- (string) ($xml->xpath('//atom:link[@rel="first"]')[0]['href'])
- );
-
- // TODO: rel="alternate" (what should this be?)
- }
-
-
- public function testTopLevelAttachmentAndNote() {
- $msg = "Top-level notes and attachments cannot be added to My Publications";
-
- // Attachment
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("attachment&linkMode=imported_file");
- $json->inPublications = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert400ForObject($response, $msg, 0);
-
- // Note
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("note");
- $json->inPublications = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert400ForObject($response, $msg, 0);
- }
-
-
- public function testLinkedFileAttachment() {
- $msg = "Linked-file attachments cannot be added to My Publications";
-
- // Create top-level item
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("book");
- $json->inPublications = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $itemKey = $json['successful'][0]['key'];
-
- $json = API::getItemTemplate("attachment&linkMode=linked_file");
- $json->inPublications = true;
- $json->parentItem = $itemKey;
- API::useAPIKey(self::$config['apiKey']);
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert400ForObject($response, $msg, 0);
- }
-
-
- public function test_should_remove_inPublications_on_POST_with_false() {
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("book");
- $json->inPublications = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
-
- $this->assert200($response);
- $key = API::getJSONFromResponse($response)['successful'][0]['key'];
- $version = $response->getHeader("Last-Modified-Version");
-
- $json = [
- "key" => $key,
- "version" => $version,
- "title" => "Test",
- "inPublications" => false
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $this->assertArrayNotHasKey('inPublications', $json['successful'][0]['data']);
- }
-
-
- public function test_shouldnt_remove_inPublications_on_POST_without_property() {
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("book");
- $json->inPublications = true;
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json])
- );
-
- $this->assert200($response);
- $key = API::getJSONFromResponse($response)['successful'][0]['key'];
- $version = $response->getHeader("Last-Modified-Version");
-
- $json = [
- "key" => $key,
- "version" => $version,
- "title" => "Test"
- ];
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([$json]),
- ["Content-Type: application/json"]
- );
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $this->assertTrue($json['successful'][0]['data']['inPublications']);
- }
-
-
- public function test_shouldnt_allow_inPublications_in_group_library() {
- API::useAPIKey(self::$config['apiKey']);
- $json = API::getItemTemplate("book");
- $json->inPublications = true;
- $response = API::groupPost(
- self::$config['ownedPrivateGroupID'],
- "items",
- json_encode([$json])
- );
-
- $this->assert400ForObject($response, "Group items cannot be added to My Publications");
- }
-
-
- private function implodeParams($params, $exclude=array()) {
- $parts = array();
- foreach ($params as $key => $val) {
- if (in_array($key, $exclude)) {
- continue;
- }
- $parts[] = $key . "=" . urlencode($val);
- }
- return implode("&", $parts);
- }
-
- private function getRandomUnicodeString() {
- return "Âéìøü 这是一个测试。 " . uniqid();
- }
-}
diff --git a/tests/remote/tests/API/3/RelationTest.php b/tests/remote/tests/API/3/RelationTest.php
deleted file mode 100644
index fe8746de..00000000
--- a/tests/remote/tests/API/3/RelationTest.php
+++ /dev/null
@@ -1,416 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class RelationTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewItemRelations() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/items/AAAAAAAA",
- "dc:relation" => array(
- "http://zotero.org/users/" . self::$config['userID'] . "/items/AAAAAAAA",
- "http://zotero.org/users/" . self::$config['userID'] . "/items/BBBBBBBB",
- )
- );
-
- $json = API::createItem("book", array(
- "relations" => $relations
- ), $this, 'jsonData');
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- if (is_string($object)) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
- else {
- foreach ($object as $rel) {
- $this->assertContains($rel, $json['relations'][$predicate]);
- }
- }
- }
- }
-
-
- public function testRelatedItemRelations() {
- $relations = [
- "owl:sameAs" => "http://zotero.org/groups/1/items/AAAAAAAA",
- ];
-
- $item1JSON = API::createItem("book", [
- "relations" => $relations
- ], $this, 'jsonData');
- $item2JSON = API::createItem("book", null, $this, 'jsonData');
-
- $uriPrefix = "http://zotero.org/users/" . self::$config['userID'] . "/items/";
- $item1URI = $uriPrefix . $item1JSON['key'];
- $item2URI = $uriPrefix . $item2JSON['key'];
-
- // Add item 2 as related item of item 1
- $relations["dc:relation"] = $item2URI;
- $item1JSON["relations"] = $relations;
- $response = API::userPut(
- self::$config['userID'],
- "items/{$item1JSON['key']}",
- json_encode($item1JSON)
- );
- $this->assert204($response);
-
- // Make sure it exists on item 1
- $json = API::getItem($item1JSON['key'], $this, 'json')['data'];
- $this->assertCount(sizeOf($relations), $json['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $json['relations'][$predicate]);
- }
-
- // And item 2, since related items are bidirectional
- $item2JSON = API::getItem($item2JSON['key'], $this, 'json')['data'];
- $this->assertCount(1, $item2JSON['relations']);
- $this->assertEquals($item1URI, $item2JSON["relations"]["dc:relation"]);
-
- // Sending item 2's unmodified JSON back up shouldn't cause the item to be updated.
- // Even though we're sending a relation that's technically not part of the item,
- // when it loads the item it will load the reverse relations too and therefore not
- // add a relation that it thinks already exists.
- $response = API::userPut(
- self::$config['userID'],
- "items/{$item2JSON['key']}",
- json_encode($item2JSON)
- );
- $this->assert204($response);
- $this->assertEquals($item2JSON['version'], $response->getHeader("Last-Modified-Version"));
- }
-
-
- // Same as above, but in a single request
- public function testRelatedItemRelationsSingleRequest() {
- $uriPrefix = "http://zotero.org/users/" . self::$config['userID'] . "/items/";
- // TEMP: Use autoloader
- require_once '../../model/ID.inc.php';
- $item1Key = \Zotero_ID::getKey();
- $item2Key = \Zotero_ID::getKey();
- $item1URI = $uriPrefix . $item1Key;
- $item2URI = $uriPrefix . $item2Key;
-
- $item1JSON = API::getItemTemplate('book');
- $item1JSON->key = $item1Key;
- $item1JSON->version = 0;
- $item1JSON->relations->{'dc:relation'} = $item2URI;
- $item2JSON = API::getItemTemplate('book');
- $item2JSON->key = $item2Key;
- $item2JSON->version = 0;
-
- $response = API::postItems([$item1JSON, $item2JSON]);
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
-
- // Make sure it exists on item 1
- $json = API::getItem($item1JSON->key, $this, 'json')['data'];
- $this->assertCount(1, $json['relations']);
- $this->assertEquals($item2URI, $json['relations']['dc:relation']);
-
- // And item 2, since related items are bidirectional
- $json = API::getItem($item2JSON->key, $this, 'json')['data'];
- $this->assertCount(1, $json['relations']);
- $this->assertEquals($item1URI, $json['relations']['dc:relation']);
- }
-
-
- public function test_should_add_a_URL_to_a_relation_with_PATCH() {
- $relations = [
- "dc:replaces" => [
- "http://zotero.org/users/" . self::$config['userID'] . "/items/AAAAAAAA"
- ]
- ];
-
- $itemJSON = API::createItem("book", [
- "relations" => $relations
- ], $this, 'jsonData');
-
- $relations["dc:replaces"][] = "http://zotero.org/users/" . self::$config['userID'] . "/items/BBBBBBBB";
-
- $patchJSON = [
- "version" => $itemJSON['version'],
- "relations" => $relations
- ];
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$itemJSON['key']}",
- json_encode($patchJSON)
- );
- $this->assert204($response);
-
- // Make sure the array was updated
- $json = API::getItem($itemJSON['key'], $this, 'json')['data'];
- $this->assertCount(sizeOf($relations), $json['relations']);
- $this->assertCount(sizeOf($relations['dc:replaces']), $json['relations']['dc:replaces']);
- $this->assertContains($relations['dc:replaces'][0], $json['relations']['dc:replaces']);
- $this->assertContains($relations['dc:replaces'][1], $json['relations']['dc:replaces']);
- }
-
-
- public function test_should_remove_a_URL_from_a_relation_with_PATCH() {
- $userID = self::$config['userID'];
-
- $relations = [
- "dc:replaces" => [
- "http://zotero.org/users/$userID/items/AAAAAAAA",
- "http://zotero.org/users/$userID/items/BBBBBBBB"
- ]
- ];
-
- $itemJSON = API::createItem("book", [
- "relations" => $relations
- ], $this, 'jsonData');
-
- $relations["dc:replaces"] = array_slice($relations["dc:replaces"], 0, 1);
-
- $patchJSON = [
- "version" => $itemJSON['version'],
- "relations" => $relations
- ];
- $response = API::userPatch(
- self::$config['userID'],
- "items/{$itemJSON['key']}",
- json_encode($patchJSON)
- );
- $this->assert204($response);
-
- // Make sure the value (now a string) was updated
- $json = API::getItem($itemJSON['key'], $this, 'json')['data'];
- $this->assertEquals($relations['dc:replaces'][0], $json['relations']['dc:replaces']);
- }
-
-
- public function testInvalidItemRelation() {
- $response = API::createItem("book", array(
- "relations" => array(
- "foo:unknown" => "http://zotero.org/groups/1/items/AAAAAAAA"
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "Unsupported predicate 'foo:unknown'");
-
- $response = API::createItem("book", array(
- "relations" => array(
- "owl:sameAs" => "Not a URI"
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "'relations' values currently must be Zotero item URIs");
-
- $response = API::createItem("book", array(
- "relations" => array(
- "owl:sameAs" => ["Not a URI"]
- )
- ), $this, 'response');
- $this->assert400ForObject($response, "'relations' values currently must be Zotero item URIs");
- }
-
-
- public function testCircularItemRelations() {
- $item1Data = API::createItem("book", null, $this, 'jsonData');
- $item2Data = API::createItem("book", null, $this, 'jsonData');
- $userID = self::$config['userID'];
-
- $item1Data['relations'] = [
- 'dc:relation' => "http://zotero.org/users/$userID/items/{$item2Data['key']}"
- ];
- $item2Data['relations'] = [
- 'dc:relation' => "http://zotero.org/users/$userID/items/{$item1Data['key']}"
- ];
- $response = API::postItems([$item1Data, $item2Data]);
- $this->assert200ForObject($response, false, 0);
- $this->assertUnchangedForObject($response, 1);
- }
-
-
- public function testDeleteItemRelation() {
- $relations = array(
- "owl:sameAs" => [
- "http://zotero.org/groups/1/items/AAAAAAAA",
- "http://zotero.org/groups/1/items/BBBBBBBB"
- ],
- "dc:relation" => "http://zotero.org/users/"
- . self::$config['userID'] . "/items/AAAAAAAA",
- );
-
- $data = API::createItem("book", array(
- "relations" => $relations
- ), $this, 'jsonData');
- $itemKey = $data['key'];
-
- // Remove a relation
- $data['relations']['owl:sameAs'] = $relations['owl:sameAs'] = $relations['owl:sameAs'][0];
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($data)
- );
- $this->assert204($response);
-
- // Make sure it's gone
- $data = API::getItem($data['key'], $this, 'json')['data'];
- $this->assertCount(sizeOf($relations), $data['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $data['relations'][$predicate]);
- }
-
- // Delete all
- $data['relations'] = new \stdClass;
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey",
- json_encode($data)
- );
- $this->assert204($response);
-
- // Make sure they're gone
- $data = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertCount(0, $data['relations']);
- }
-
-
- //
- // Collections
- //
- public function testNewCollectionRelations() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- );
-
- $data = API::createCollection("Test", array(
- "relations" => $relations
- ), $this, 'jsonData');
- $this->assertCount(sizeOf($relations), $data['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $data['relations'][$predicate]);
- }
- }
-
-
- public function testInvalidCollectionRelation() {
- $json = [
- "name" => "Test",
- "relations" => array(
- "foo:unknown" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- )
- ];
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([$json])
- );
- $this->assert400ForObject($response, "Unsupported predicate 'foo:unknown'");
-
- $json["relations"] = array(
- "owl:sameAs" => "Not a URI"
- );
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([$json])
- );
- $this->assert400ForObject($response, "'relations' values currently must be Zotero collection URIs");
-
- $json["relations"] = ["http://zotero.org/groups/1/collections/AAAAAAAA"];
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([$json])
- );
- $this->assert400ForObject($response, "'relations' property must be an object");
- }
-
-
- public function testDeleteCollectionRelation() {
- $relations = array(
- "owl:sameAs" => "http://zotero.org/groups/1/collections/AAAAAAAA"
- );
- $data = API::createCollection("Test", array(
- "relations" => $relations
- ), $this, 'jsonData');
-
- // Remove all relations
- $data['relations'] = new \stdClass;
- unset($relations['owl:sameAs']);
- $response = API::userPut(
- self::$config['userID'],
- "collections/{$data['key']}",
- json_encode($data)
- );
- $this->assert204($response);
-
- // Make sure it's gone
- $data = API::getCollection($data['key'], $this, 'json')['data'];
- $this->assertCount(sizeOf($relations), $data['relations']);
- foreach ($relations as $predicate => $object) {
- $this->assertEquals($object, $data['relations'][$predicate]);
- }
- }
-
-
- public function test_should_return_200_for_values_for_mendeleyDB_collection_relation() {
- $relations = [
- "mendeleyDB:remoteFolderUUID" => "b95b84b9-8b27-4a55-b5ea-5b98c1cac205"
- ];
- $data = API::createCollection(
- "Test",
- [
- "relations" => $relations
- ],
- $this,
- 'jsonData'
- );
- $this->assertEquals($relations['mendeleyDB:remoteFolderUUID'], $data['relations']['mendeleyDB:remoteFolderUUID']);
- }
-
-
- public function test_should_return_200_for_arrays_for_mendeleyDB_collection_relation() {
- $json = [
- "name" => "Test",
- "relations" => [
- "mendeleyDB:remoteFolderUUID" => ["b95b84b9-8b27-4a55-b5ea-5b98c1cac205"]
- ]
- ];
- $response = API::userPost(
- self::$config['userID'],
- "collections",
- json_encode([$json])
- );
- $this->assert200ForObject($response);
- }
-}
diff --git a/tests/remote/tests/API/3/SearchTest.php b/tests/remote/tests/API/3/SearchTest.php
deleted file mode 100644
index dae71383..00000000
--- a/tests/remote/tests/API/3/SearchTest.php
+++ /dev/null
@@ -1,330 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class SearchTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testNewSearch() {
- $name = "Test Search";
- $conditions = [
- [
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- ],
- [
- "condition" => "noChildren",
- "operator" => "false",
- "value" => ""
- ],
- [
- "condition" => "fulltextContent/regexp",
- "operator" => "contains",
- "value" => "/test/"
- ]
- ];
-
- // DEBUG: Should fail with no version?
- $response = API::userPost(
- self::$config['userID'],
- "searches",
- json_encode([[
- "name" => $name,
- "conditions" => $conditions
- ]]),
- ["Content-Type: application/json"]
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(1, $json['successful']);
- // Deprecated
- $this->assertCount(1, $json['success']);
-
- // Check data in write response
- $data = $json['successful'][0]['data'];
- $this->assertEquals($json['successful'][0]['key'], $data['key']);
- $this->assertEquals($libraryVersion, $data['version']);
- $this->assertEquals($libraryVersion, $data['version']);
- $this->assertEquals($name, $data['name']);
- $this->assertInternalType('array', $data['conditions']);
- $this->assertCount(sizeOf($conditions), $data['conditions']);
- foreach ($conditions as $i => $condition) {
- foreach ($condition as $key => $val) {
- $this->assertEquals($val, $data['conditions'][$i][$key]);
- }
- }
-
-
- // Check in separate request, to be safe
- $keys = array_map(function ($o) {
- return $o['key'];
- }, $json['successful']);
- $response = API::getSearchResponse($keys);
- $this->assertTotalResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $data = $json[0]['data'];
- $this->assertEquals($name, $data['name']);
- $this->assertInternalType('array', $data['conditions']);
- $this->assertCount(sizeOf($conditions), $data['conditions']);
- foreach ($conditions as $i => $condition) {
- foreach ($condition as $key => $val) {
- $this->assertEquals($val, $data['conditions'][$i][$key]);
- }
- }
-
- return $data;
- }
-
-
- /**
- * @depends testNewSearch
- */
- public function testModifySearch($data) {
- $key = $data['key'];
- $version = $data['version'];
-
- // Remove one search condition
- array_shift($data['conditions']);
-
- $name = $data['name'];
- $conditions = $data['conditions'];
-
- $response = API::userPut(
- self::$config['userID'],
- "searches/$key",
- json_encode($data),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert204($response);
-
- $data = API::getSearch($key, $this, 'json')['data'];
- $this->assertEquals($name, (string) $data['name']);
- $this->assertInternalType('array', $data['conditions']);
- $this->assertCount(sizeOf($conditions), $data['conditions']);
- foreach ($conditions as $i => $condition) {
- foreach ($condition as $key => $val) {
- $this->assertEquals($val, $data['conditions'][$i][$key]);
- }
- }
- }
-
-
- public function testEditMultipleSearches() {
- $search1Name = "Test 1";
- $search1Conditions = [
- [
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- ]
- ];
- $search1Data = API::createSearch($search1Name, $search1Conditions, $this, 'jsonData');
- $search1NewName = "Test 1 Modified";
-
- $search2Name = "Test 2";
- $search2Conditions = [
- [
- "condition" => "title",
- "operator" => "is",
- "value" => "test2"
- ]
- ];
- $search2Data = API::createSearch($search2Name, $search2Conditions, $this, 'jsonData');
- $search2NewConditions = [
- [
- "condition" => "title",
- "operator" => "isNot",
- "value" => "test1"
- ]
- ];
-
- $response = API::userPost(
- self::$config['userID'],
- "searches",
- json_encode([
- [
- 'key' => $search1Data['key'],
- 'version' => $search1Data['version'],
- 'name' => $search1NewName
- ],
- [
- 'key' => $search2Data['key'],
- 'version' => $search2Data['version'],
- 'conditions' => $search2NewConditions
- ]
- ]),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertCount(2, $json['successful']);
- // Deprecated
- $this->assertCount(2, $json['success']);
-
- // Check data in write response
- $this->assertEquals($json['successful'][0]['key'], $json['successful'][0]['data']['key']);
- $this->assertEquals($json['successful'][1]['key'], $json['successful'][1]['data']['key']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['version']);
- $this->assertEquals($libraryVersion, $json['successful'][0]['data']['version']);
- $this->assertEquals($libraryVersion, $json['successful'][1]['data']['version']);
- $this->assertEquals($search1NewName, $json['successful'][0]['data']['name']);
- $this->assertEquals($search2Name, $json['successful'][1]['data']['name']);
- $this->assertEquals($search1Conditions, $json['successful'][0]['data']['conditions']);
- $this->assertEquals($search2NewConditions, $json['successful'][1]['data']['conditions']);
-
- // Check in separate request, to be safe
- $keys = array_map(function ($o) {
- return $o['key'];
- }, $json['successful']);
- $response = API::getSearchResponse($keys);
- $this->assertTotalResults(2, $response);
- $json = API::getJSONFromResponse($response);
- // POST follows PATCH behavior, so unspecified values shouldn't change
- $this->assertEquals($search1NewName, $json[0]['data']['name']);
- $this->assertEquals($search1Conditions, $json[0]['data']['conditions']);
- $this->assertEquals($search2Name, $json[1]['data']['name']);
- $this->assertEquals($search2NewConditions, $json[1]['data']['conditions']);
- }
-
-
- public function testNewSearchNoName() {
- $json = API::createSearch(
- "",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responseJSON'
- );
- $this->assert400ForObject($json, "Search name cannot be empty");
- }
-
-
- public function test_should_allow_a_search_with_emoji_values() {
- $json = API::createSearch(
- "🐶", // 4-byte character
- [
- [
- "condition" => "title",
- "operator" => "contains",
- "value" => "🐶" // 4-byte character
- ]
- ],
- $this,
- 'responseJSON'
- );
- $this->assert200ForObject($json);
- }
-
-
- public function testNewSearchNoConditions() {
- $json = API::createSearch("Test", array(), $this, 'responseJSON');
- $this->assert400ForObject($json, "'conditions' cannot be empty");
- }
-
-
- public function testNewSearchConditionErrors() {
- $json = API::createSearch(
- "Test",
- array(
- array(
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responseJSON'
- );
- $this->assert400ForObject($json, "'condition' property not provided for search condition");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'responseJSON'
- );
- $this->assert400ForObject($json, "Search condition cannot be empty");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "title",
- "value" => "test"
- )
- ),
- $this,
- 'responseJSON'
- );
- $this->assert400ForObject($json, "'operator' property not provided for search condition");
-
- $json = API::createSearch(
- "Test",
- array(
- array(
- "condition" => "title",
- "operator" => "",
- "value" => "test"
- )
- ),
- $this,
- 'responseJSON'
- );
- $this->assert400ForObject($json, "Search operator cannot be empty");
- }
-}
diff --git a/tests/remote/tests/API/3/SettingsTest.php b/tests/remote/tests/API/3/SettingsTest.php
deleted file mode 100644
index 853e970a..00000000
--- a/tests/remote/tests/API/3/SettingsTest.php
+++ /dev/null
@@ -1,663 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class SettingsTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function tearDown() {
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- }
-
-
- public function testAddUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = array(
- "value" => $value
- );
-
- // No version
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert428($response);
-
- // Version must be 0 for non-existent setting
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 1"
- )
- );
- $this->assert412($response);
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- )
- );
- $this->assert204($response);
-
- // Multi-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']);
-
- // Single-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
- }
-
-
- public function testAddUserSettingMultiple() {
- $json = [
- "tagColors" => [
- "value" => [
- [
- "name" => "_READ",
- "color" => "#990000"
- ]
- ]
- ],
- "feeds" => [
- "value" => [
- "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml" => [
- "url" => "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml",
- "name" => "NYT > Home Page",
- "cleanupAfter" => 2,
- "refreshInterval" => 60
- ]
- ]
- ]
- ];
- $settingKeys = array_keys($json);
- $json = json_decode(json_encode($json));
-
- $libraryVersion = API::getLibraryVersion();
-
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
- $this->assertEquals(++$libraryVersion, $response->getHeader("Last-Modified-Version"));
-
- // Multi-object GET
- $response = API::userGet(
- self::$config['userID'],
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json2 = json_decode($response->getBody());
- $this->assertNotNull($json2);
- foreach ($settingKeys as $settingKey) {
- $this->assertObjectHasAttribute($settingKey, $json2, "Object should have $settingKey property");
- $this->assertEquals($json->$settingKey->value, $json2->$settingKey->value, "'$settingKey' value should match");
- $this->assertEquals($libraryVersion, $json2->$settingKey->version, "'$settingKey' version should match");
- }
-
- // Single-object GET
- foreach ($settingKeys as $settingKey) {
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json2 = json_decode($response->getBody());
- $this->assertNotNull($json2);
- $this->assertEquals($json->$settingKey->value, $json2->value);
- $this->assertEquals($libraryVersion, $json2->version);
- }
- }
-
-
- public function testAddGroupSettingMultiple() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- // TODO: multiple, once more settings are supported
-
- $groupID = self::$config['ownedPrivateGroupID'];
- $libraryVersion = API::getGroupLibraryVersion($groupID);
-
- $json = array(
- $settingKey => array(
- "value" => $value
- )
- );
- $response = API::groupPost(
- $groupID,
- "settings",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Multi-object GET
- $response = API::groupGet(
- $groupID,
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion + 1, $json[$settingKey]['version']);
-
- // Single-object GET
- $response = API::groupGet(
- $groupID,
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
- }
-
-
- public function testUpdateUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- // Update with no change
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($value, $json['value']);
- $this->assertEquals($libraryVersion + 1, $json['version']);
-
- $newValue = array(
- array(
- "name" => "_READ",
- "color" => "#CC9933"
- )
- );
- $json['value'] = $newValue;
-
- // Update, no change
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertEquals($newValue, $json['value']);
- $this->assertEquals($libraryVersion + 2, $json['version']);
- }
-
-
- public function testUpdateUserSettings() {
- $settingKey = "tagColors";
- $value = [
- [
- "name" => "_READ",
- "color" => "#990000"
- ]
- ];
-
- $libraryVersion = API::getLibraryVersion();
-
- $json = [
- "value" => $value,
- "version" => 0
- ];
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert204($response);
- $this->assertEquals(++$libraryVersion, $response->getHeader('Last-Modified-Version'));
-
- $response = API::userGet(
- self::$config['userID'],
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $this->assertEquals($libraryVersion, $response->getHeader('Last-Modified-Version'));
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion, $json[$settingKey]['version']);
-
- // Update with no change
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode([
- $settingKey => [
- "value" => $value
- ]
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $libraryVersion"
- ]
- );
- $this->assert204($response);
- $this->assertEquals($libraryVersion, $response->getHeader('Last-Modified-Version'));
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $this->assertEquals($libraryVersion, $response->getHeader('Last-Modified-Version'));
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($value, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion, $json[$settingKey]['version']);
-
- $newValue = [
- [
- "name" => "_READ",
- "color" => "#CC9933"
- ]
- ];
-
- // Update
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode([
- $settingKey => [
- "value" => $newValue
- ]
- ]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $libraryVersion"
- ]
- );
- $this->assert204($response);
- $this->assertEquals(++$libraryVersion, $response->getHeader('Last-Modified-Version'));
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings"
- );
- $this->assert200($response);
- $this->assertContentType("application/json", $response);
- $this->assertEquals($libraryVersion, $response->getHeader('Last-Modified-Version'));
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertArrayHasKey($settingKey, $json);
- $this->assertEquals($newValue, $json[$settingKey]['value']);
- $this->assertEquals($libraryVersion, $json[$settingKey]['version']);
- }
-
-
- public function testDeleteUserSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => "_READ",
- "color" => "#990000"
- )
- );
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- // Create
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert204($response);
-
- // Delete
- $response = API::userDelete(
- self::$config['userID'],
- "settings/$settingKey",
- array(
- "If-Unmodified-Since-Version: " . ($libraryVersion + 1)
- )
- );
- $this->assert204($response);
-
- // Check
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert404($response);
-
- $this->assertEquals($libraryVersion + 2, API::getLibraryVersion());
- }
-
-
- public function testDeleteNonexistentSetting() {
- $response = API::userDelete(
- self::$config['userID'],
- "settings/nonexistentSetting",
- array(
- "If-Unmodified-Since-Version: 0"
- )
- );
- $this->assert404($response);
- }
-
-
- public function testSettingsSince() {
- $libraryVersion1 = API::getLibraryVersion();
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode([
- "tagColors" => [
- "value" => [
- [
- "name" => "_READ",
- "color" => "#990000"
- ]
- ]
- ]
- ])
- );
- $this->assert204($response);
- $libraryVersion2 = $response->getHeader("Last-Modified-Version");
-
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode([
- "feeds" => [
- "value" => [
- "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml" => [
- "url" => "http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml",
- "name" => "NYT > Home Page",
- "cleanupAfter" => 2,
- "refreshInterval" => 60
- ]
- ]
- ]
- ])
- );
- $this->assert204($response);
- $libraryVersion3 = $response->getHeader("Last-Modified-Version");
-
- $response = API::userGet(
- self::$config['userID'],
- "settings?since=$libraryVersion1"
- );
- $this->assertNumResults(2, $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "settings?since=$libraryVersion2"
- );
- $this->assertNumResults(1, $response);
-
- $response = API::userGet(
- self::$config['userID'],
- "settings?since=$libraryVersion3"
- );
- $this->assertNumResults(0, $response);
- }
-
-
- public function testUnsupportedSetting() {
- $settingKey = "unsupportedSetting";
- $value = true;
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Invalid setting '$settingKey'");
- }
-
-
- public function testUnsupportedSettingMultiple() {
- $settingKey = "unsupportedSetting";
- $json = array(
- "tagColors" => array(
- "value" => array(
- "name" => "_READ",
- "color" => "#990000"
- ),
- "version" => 0
- ),
- $settingKey => array(
- "value" => false,
- "version" => 0
- )
- );
-
- $libraryVersion = API::getLibraryVersion();
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Invalid setting '$settingKey'");
-
- // Valid setting shouldn't exist, and library version should be unchanged
- $response = API::userGet(
- self::$config['userID'],
- "settings/$settingKey"
- );
- $this->assert404($response);
- $this->assertEquals($libraryVersion, API::getLibraryVersion());
- }
-
-
- public function testOverlongSetting() {
- $settingKey = "tagColors";
- $value = array(
- array(
- "name" => $this->content = str_repeat("abcdefghij", 2001),
- "color" => "#990000"
- )
- );
-
- $json = array(
- "value" => $value,
- "version" => 0
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "'value' cannot be longer than 20000 characters");
- }
-
-
- public function test_should_allow_emoji_character() {
- $settingKey = "tagColors";
- $value = [
- [
- "name" => "🐶",
- "color" => "#990000"
- ]
- ];
- $json = [
- "value" => $value,
- "version" => 0
- ];
- $response = API::userPut(
- self::$config['userID'],
- "settings/$settingKey",
- json_encode($json),
- ["Content-Type: application/json"]
- );
- $this->assert204($response);
- }
-}
diff --git a/tests/remote/tests/API/3/SortTest.php b/tests/remote/tests/API/3/SortTest.php
deleted file mode 100644
index 08be64e3..00000000
--- a/tests/remote/tests/API/3/SortTest.php
+++ /dev/null
@@ -1,517 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class SortTests extends APITests {
- private static $collectionKeys = [];
- private static $itemKeys = [];
- private static $childAttachmentKeys = [];
- private static $childNoteKeys = [];
- private static $searchKeys = [];
-
- private static $titles = ['q', 'c', 'a', 'j', 'e', 'h', 'i'];
- private static $names = ['m', 's', 'a', 'bb', 'ba', '', ''];
- private static $attachmentTitles = ['v', 'x', null, 'a', null];
- private static $notes = [null, 'aaa', null, null, 'taf'];
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
-
- //
- // Collections
- //
- /*for ($i=0; $i<5; $i++) {
- self::$collectionKeys[] = API::createCollection("Test", false, null, 'key');
- }*/
-
- //
- // Items
- //
- $titles = self::$titles;
- $names = self::$names;
- for ($i = 0; $i < sizeOf(self::$titles) - 2; $i++) {
- $key = API::createItem("book", [
- 'title' => array_shift($titles),
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => array_shift($names)
- ]
- ]
- ], null, 'key');
-
- // Child attachments
- if (!is_null(self::$attachmentTitles[$i])) {
- self::$childAttachmentKeys[] = API::createAttachmentItem("imported_file", [
- 'title' => self::$attachmentTitles[$i]
- ], $key, null, 'key');
- }
- // Child notes
- if (!is_null(self::$notes[$i])) {
- self::$childNoteKeys[] = API::createNoteItem(self::$notes[$i], $key, null, 'key');
- }
-
- self::$itemKeys[] = $key;
- }
- // Top-level attachment
- self::$itemKeys[] = API::createAttachmentItem("imported_file", [
- 'title' => array_shift($titles)
- ], false, null, 'key');
- // Top-level note
- self::$itemKeys[] = API::createNoteItem(array_shift($titles), false, null, 'key');
-
- //
- // Searches
- //
- /*for ($i=0; $i<5; $i++) {
- self::$searchKeys[] = API::createSearch("Test", 'default', null, 'key');
- }*/
- }
-
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testSortTopItemsTitle() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&sort=title"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $titles = self::$titles;
- asort($titles);
- $this->assertCount(sizeOf($titles), $keys);
- $correct = [];
- foreach ($titles as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- $this->assertEquals($correct, $keys);
- }
-
-
- // Same thing, but with order parameter for backwards compatibility
- public function testSortTopItemsTitleOrder() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&order=title"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $titles = self::$titles;
- asort($titles);
- $this->assertCount(sizeOf($titles), $keys);
- $correct = [];
- foreach ($titles as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- $this->assertEquals($correct, $keys);
- }
-
-
- public function testSortTopItemsCreator() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&sort=creator"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $names = self::$names;
- uasort($names, function ($a, $b) {
- if ($a === '' && $b !== '') return 1;
- if ($b === '' && $a !== '') return -1;
- return strcmp($a, $b);
- });
- $this->assertCount(sizeOf($names), $keys);
- $endKeys = array_splice($keys, -2);
- $correct = [];
- foreach ($names as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- // Remove empty names
- array_splice($correct, -2);
- $this->assertEquals($correct, $keys);
- // Check attachment and note, which should fall back to ordered added (itemID)
- $this->assertEquals(array_slice(self::$itemKeys, -2), $endKeys);
- }
-
-
- // Same thing, but with 'order' for backwards compatibility
- public function testSortTopItemsCreatorOrder() {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&order=creator"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $names = self::$names;
- uasort($names, function ($a, $b) {
- if ($a === '' && $b !== '') return 1;
- if ($b === '' && $a !== '') return -1;
- return strcmp($a, $b);
- });
- $this->assertCount(sizeOf($names), $keys);
- $endKeys = array_splice($keys, -2);
- $correct = [];
- foreach ($names as $k => $v) {
- // The key at position k in itemKeys should be at the same position in keys
- $correct[] = self::$itemKeys[$k];
- }
- // Remove empty names
- array_splice($correct, -2);
- $this->assertEquals($correct, $keys);
- // Check attachment and note, which should fall back to ordered added (itemID)
- $this->assertEquals(array_slice(self::$itemKeys, -2), $endKeys);
- }
-
-
- // Old sort=asc, with no 'order' param
- public function testSortSortParamAsDirectionWithoutOrder() {
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&sort=asc"
- );
- // We can't test dateAdded without adding lots of delays,
- // so just make sure this doesn't throw an error
- $this->assert200($response);
- }
-
-
- //
- //
- // Test below here do their own clears
- //
- //
-
-
- public function testSortDefault() {
- API::userClear(self::$config['userID']);
-
- // Setup
- $dataArray = [];
-
- $dataArray[] = API::createItem("book", [
- 'title' => "B",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "B"
- ]
- ],
- 'dateAdded' => '2014-02-05T00:00:00Z',
- 'dateModified' => '2014-04-05T01:00:00Z'
- ], $this, 'jsonData');
-
- $dataArray[] = API::createItem("journalArticle", [
- 'title' => "A",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "A"
- ]
- ],
- 'dateAdded' => '2014-02-04T00:00:00Z',
- 'dateModified' => '2014-01-04T01:00:00Z'
- ], $this, 'jsonData');
-
- $dataArray[] = API::createItem("newspaperArticle", [
- 'title' => "F",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "F"
- ]
- ],
- 'dateAdded' => '2014-02-03T00:00:00Z',
- 'dateModified' => '2014-02-03T01:00:00Z'
-
- ], $this, 'jsonData');
-
-
- $dataArray[] = API::createItem("book", [
- 'title' => "C",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "C"
- ]
- ],
- 'dateAdded' => '2014-02-02T00:00:00Z',
- 'dateModified' => '2014-03-02T01:00:00Z'
- ], $this, 'jsonData');
-
- // Get sorted keys
- usort($dataArray, function ($a, $b) {
- return strcmp($a['dateAdded'], $b['dateAdded']) * -1;
- });
- $keysByDateAddedDescending = array_map(function ($data) {
- return $data['key'];
- }, $dataArray);
- usort($dataArray, function ($a, $b) {
- return strcmp($a['dateModified'], $b['dateModified']) * -1;
- });
- $keysByDateModifiedDescending = array_map(function ($data) {
- return $data['key'];
- }, $dataArray);
-
- // Tests
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateModifiedDescending, explode("\n", trim($response->getBody())));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $keys = array_map(function ($val) {
- return $val['key'];
- }, $json);
- $this->assertEquals($keysByDateModifiedDescending, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $keys = array_map(function ($val) {
- return (string) $val;
- }, $xml->xpath('//atom:entry/zapi:key'));
- $this->assertEquals($keysByDateAddedDescending, $keys);
- }
-
-
- public function testSortDirection() {
- API::userClear(self::$config['userID']);
-
- // Setup
- $dataArray = [];
-
- $dataArray[] = API::createItem("book", [
- 'title' => "B",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "B"
- ]
- ],
- 'dateAdded' => '2014-02-05T00:00:00Z',
- 'dateModified' => '2014-04-05T01:00:00Z'
- ], $this, 'jsonData');
-
- $dataArray[] = API::createItem("journalArticle", [
- 'title' => "A",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "A"
- ]
- ],
- 'dateAdded' => '2014-02-04T00:00:00Z',
- 'dateModified' => '2014-01-04T01:00:00Z'
- ], $this, 'jsonData');
-
- $dataArray[] = API::createItem("newspaperArticle", [
- 'title' => "F",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "F"
- ]
- ],
- 'dateAdded' => '2014-02-03T00:00:00Z',
- 'dateModified' => '2014-02-03T01:00:00Z'
-
- ], $this, 'jsonData');
-
-
- $dataArray[] = API::createItem("book", [
- 'title' => "C",
- 'creators' => [
- [
- "creatorType" => "author",
- "name" => "C"
- ]
- ],
- 'dateAdded' => '2014-02-02T00:00:00Z',
- 'dateModified' => '2014-03-02T01:00:00Z'
- ], $this, 'jsonData');
-
- // Get sorted keys
- usort($dataArray, function ($a, $b) {
- return strcmp($a['dateAdded'], $b['dateAdded']);
- });
- $keysByDateAddedAscending = array_map(function ($data) {
- return $data['key'];
- }, $dataArray);
-
- $keysByDateAddedDescending = array_reverse($keysByDateAddedAscending);
-
- // Ascending
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&sort=dateAdded&direction=asc"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateAddedAscending, explode("\n", trim($response->getBody())));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json&sort=dateAdded&direction=asc"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $keys = array_map(function ($val) {
- return $val['key'];
- }, $json);
- $this->assertEquals($keysByDateAddedAscending, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom&sort=dateAdded&direction=asc"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $keys = array_map(function ($val) {
- return (string) $val;
- }, $xml->xpath('//atom:entry/zapi:key'));
- $this->assertEquals($keysByDateAddedAscending, $keys);
-
- // Ascending using old 'order'/'sort' instead of 'sort'/'direction'
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&order=dateAdded&sort=asc"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateAddedAscending, explode("\n", trim($response->getBody())));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json&order=dateAdded&sort=asc"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $keys = array_map(function ($val) {
- return $val['key'];
- }, $json);
- $this->assertEquals($keysByDateAddedAscending, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom&order=dateAdded&sort=asc"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $keys = array_map(function ($val) {
- return (string) $val;
- }, $xml->xpath('//atom:entry/zapi:key'));
- $this->assertEquals($keysByDateAddedAscending, $keys);
-
- // Deprecated 'order'/'sort', but the wrong way
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&sort=dateAdded&order=asc"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateAddedAscending, explode("\n", trim($response->getBody())));
-
- // Descending
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&sort=dateAdded&direction=desc"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateAddedDescending, explode("\n", trim($response->getBody())));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json&sort=dateAdded&direction=desc"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $keys = array_map(function ($val) {
- return $val['key'];
- }, $json);
- $this->assertEquals($keysByDateAddedDescending, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom&sort=dateAdded&direction=desc"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $keys = array_map(function ($val) {
- return (string) $val;
- }, $xml->xpath('//atom:entry/zapi:key'));
- $this->assertEquals($keysByDateAddedDescending, $keys);
-
- // Descending
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&order=dateAdded&sort=desc"
- );
- $this->assert200($response);
- $this->assertEquals($keysByDateAddedDescending, explode("\n", trim($response->getBody())));
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=json&order=dateAdded&sort=desc"
- );
- $this->assert200($response);
- $json = API::getJSONFromResponse($response);
- $keys = array_map(function ($val) {
- return $val['key'];
- }, $json);
- $this->assertEquals($keysByDateAddedDescending, $keys);
-
- $response = API::userGet(
- self::$config['userID'],
- "items?format=atom&order=dateAdded&sort=desc"
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $keys = array_map(function ($val) {
- return (string) $val;
- }, $xml->xpath('//atom:entry/zapi:key'));
- $this->assertEquals($keysByDateAddedDescending, $keys);
- }
-}
diff --git a/tests/remote/tests/API/3/StorageAdminTest.php b/tests/remote/tests/API/3/StorageAdminTest.php
deleted file mode 100644
index e7b3ad05..00000000
--- a/tests/remote/tests/API/3/StorageAdminTest.php
+++ /dev/null
@@ -1,113 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/bootstrap.inc.php';
-
-class StorageAdminTests extends APITests {
- const DEFAULT_QUOTA = 300;
-
- private static $toDelete = array();
-
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- }
-
- public function setUp() {
- parent::setUp();
-
- // Clear subscription
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- 'quota=0&expiration=0',
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals(self::DEFAULT_QUOTA, (int) $xml->quota);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
-
- // Clear subscription
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- 'quota=0&expiration=0',
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- }
-
-
- public function test2GB() {
- $quota = 2000;
- $expiration = time() + (86400 * 365);
-
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- "quota=$quota&expiration=$expiration",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals($quota, (int) $xml->quota);
- $this->assertEquals($expiration, (int) $xml->expiration);
- }
-
-
- public function testUnlimited() {
- $quota = 'unlimited';
- $expiration = time() + (86400 * 365);
-
- $response = API::post(
- 'users/' . self::$config['userID'] . '/storageadmin',
- "quota=$quota&expiration=$expiration",
- [],
- [
- "username" => self::$config['rootUsername'],
- "password" => self::$config['rootPassword']
- ]
- );
- $this->assert200($response);
- $xml = API::getXMLFromResponse($response);
- $this->assertEquals($quota, (string) $xml->quota);
- $this->assertEquals($expiration, (int) $xml->expiration);
- }
-}
diff --git a/tests/remote/tests/API/3/TagTest.php b/tests/remote/tests/API/3/TagTest.php
deleted file mode 100644
index e5a2961a..00000000
--- a/tests/remote/tests/API/3/TagTest.php
+++ /dev/null
@@ -1,653 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class TagTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- require 'include/config.inc.php';
- API::userClear($config['userID']);
- }
-
-
-
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
- public function test_empty_tag_should_be_ignored() {
- $json = API::getItemTemplate("book");
- $json->tags[] = [
- "tag" => "A"
- ];
- $json->tags[] = [
- "tag" => "",
- "type" => 1
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $json = $json['successful'][0]['data'];
- $this->assertSame($json['tags'], [['tag' => 'A']]);
- }
-
- public function test_empty_tag_with_whitespace_should_be_ignored() {
- $json = API::getItemTemplate("book");
- $json->tags[] = [
- "tag" => "A"
- ];
- $json->tags[] = [
- "tag" => " ",
- "type" => 1
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $json = $json['successful'][0]['data'];
- $this->assertSame($json['tags'], [['tag' => 'A']]);
- }
-
- public function testInvalidTagObject() {
- $json = API::getItemTemplate("book");
- $json->tags[] = array("invalid");
-
- $response = API::postItem($json);
- $this->assert400ForObject($response, "Tag must be an object");
- }
-
-
- public function test_should_add_tag_to_item() {
- $json = API::getItemTemplate("book");
- $json->tags[] = [
- "tag" => "A"
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
-
- $json = $json['successful'][0]['data'];
- $json['tags'][] = [
- "tag" => "C"
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
-
- $json = $json['successful'][0]['data'];
- $json['tags'][] = [
- "tag" => "B"
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
-
- $json = $json['successful'][0]['data'];
- $json['tags'][] = [
- "tag" => "D"
- ];
- $response = API::postItem($json);
- $this->assert200ForObject($response);
- $tags = $json['tags'];
- $json = API::getJSONFromResponse($response);
-
- $json = $json['successful'][0]['data'];
- $this->assertSame($tags, $json['tags']);
- }
-
-
- public function test_utf8mb4_tag() {
- $json = API::getItemTemplate("book");
- $json->tags[] = [
- "tag" => "🐻", // 4-byte character
- "type" => 0
- ];
-
- $response = API::postItem($json);
- $this->assert200ForObject($response);
-
- $newJSON = API::getJSONFromResponse($response);
- $newJSON = $newJSON['successful'][0]['data'];
- $this->assertCount(1, $newJSON['tags']);
- $this->assertEquals($json->tags[0]['tag'], $newJSON['tags'][0]['tag']);
- }
-
-
- public function testTagTooLong() {
- $tag = \Zotero_Utilities::randomString(300);
- $json = API::getItemTemplate("book");
- $json->tags[] = [
- "tag" => $tag,
- "type" => 1
- ];
-
- $response = API::postItem($json);
- $this->assert413ForObject($response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($tag, $json['failed'][0]['data']['tag']);
- }
-
-
- public function testItemTagSearch() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- $key1 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this, 'key');
-
- $key2 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this, 'key');
-
- //
- // Searches
- //
-
- // a (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=a"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // a and c (#2)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=a&tag=c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key2, $keys);
-
- // b and c (none)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=b&tag=c"
- );
- $this->assert200($response);
- $this->assertEmpty(trim($response->getBody()));
-
- // b or c (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=b%20||%20c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // a or b or c (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=a%20||%20b%20||%20c"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // not a (none)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=-a"
- );
- $this->assert200($response);
- $this->assertEmpty(trim($response->getBody()));
-
- // not b (#2)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=-b"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key2, $keys);
-
- // (b or c) and a (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=b%20||%20c&tag=a"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // not nonexistent (both)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=-z"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(2, $keys);
- $this->assertContains($key1, $keys);
- $this->assertContains($key2, $keys);
-
- // A (case-insensitive search)
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&tag=B"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key1, $keys);
- }
-
-
- public function test_should_handle_negation_in_top_requests() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- $key1 = API::createItem("book", array(
- "tags" => [
- ["tag" => "a"],
- ["tag" => "b"]
- ]
- ), $this, 'key');
-
- $key2 = API::createItem("book", array(
- "tags" => [
- ["tag" => "a"],
- ["tag" => "c"]
- ]
- ), $this, 'key');
- API::createAttachmentItem("imported_url", [], $key1, $this, 'jsonData');
- API::createAttachmentItem("imported_url", [], $key2, $this, 'jsonData');
-
- // not b in /top (#2)
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=keys&tag=-b"
- );
- $this->assert200($response);
- $keys = explode("\n", trim($response->getBody()));
- $this->assertCount(1, $keys);
- $this->assertContains($key2, $keys);
- }
-
-
- public function testKeyedItemWithTags() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- require_once '../../model/ID.inc.php';
- $itemKey = \Zotero_ID::getKey();
- $json = API::createItem("book", [
- "key" => $itemKey,
- "version" => 0,
- "tags" => [
- ["tag" => "a"],
- ["tag" => "b"]
- ]
- ], $this, 'responseJSON');
-
- $json = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertCount(2, $json['tags']);
- $this->assertContains(['tag' => 'a'], $json['tags']);
- $this->assertContains(['tag' => 'b'], $json['tags']);
- }
-
-
- public function testTagSearch() {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
-
- $itemKey1 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'key');
-
- $itemKey2 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags2)
- ), $this, 'key');
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?tag=" . implode("%20||%20", $tags1)
- );
- $this->assert200($response);
- $this->assertNumResults(sizeOf($tags1), $response);
- }
-
-
- public function testOrphanedTag() {
- $json = API::createItem("book", array(
- "tags" => [["tag" => "a"]]
- ), $this, 'jsonData');
- $libraryVersion1 = $json['version'];
- $itemKey1 = $json['key'];
-
- $json = API::createItem("book", array(
- "tags" => [["tag" => "b"]]
- ), $this, 'jsonData');
- $itemKey2 = $json['key'];
-
- $json = API::createItem("book", array(
- "tags" => [["tag" => "b"]]
- ), $this, 'jsonData');
- $itemKey3 = $json['key'];
-
- $response = API::userDelete(
- self::$config['userID'],
- "items/$itemKey1",
- array("If-Unmodified-Since-Version: $libraryVersion1")
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "tags"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response)[0];
- $this->assertEquals("b", $json['tag']);
- }
-
-
- public function testTagNewer() {
- API::userClear(self::$config['userID']);
-
- // Create items with tags
- API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this);
-
- $version = API::getLibraryVersion();
-
- // 'newer' shouldn't return any results
- $response = API::userGet(
- self::$config['userID'],
- "tags?newer=$version"
- );
- $this->assert200($response);
- $this->assertNumResults(0, $response);
-
- // Create another item with tags
- API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this);
-
- // 'newer' should return new tag (Atom)
- $response = API::userGet(
- self::$config['userID'],
- "tags?content=json&newer=$version"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $this->assertGreaterThan($version, $response->getHeader('Last-Modified-Version'));
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $data = json_decode($data['content'], true);
- $this->assertEquals("c", $data['tag']);
- $this->assertEquals(0, $data['type']);
-
- // 'newer' should return new tag (JSON)
- $response = API::userGet(
- self::$config['userID'],
- "tags?newer=$version"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $this->assertGreaterThan($version, $response->getHeader('Last-Modified-Version'));
- $json = API::getJSONFromResponse($response)[0];
- $this->assertEquals("c", $json['tag']);
- $this->assertEquals(0, $json['meta']['type']);
- }
-
-
- public function testMultiTagDelete() {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
- $tags3 = array("Foo");
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'key');
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag, "type" => 1);
- }, $tags2)
- ), $this, 'key');
-
- API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags3)
- ), $this, 'key');
-
- $libraryVersion = API::getLibraryVersion();
-
- // Missing version header
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=" . implode("%20||%20", array_merge($tags1, $tags2))
- );
- $this->assert428($response);
-
- // Outdated version header
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=" . implode("%20||%20", array_merge($tags1, $tags2)),
- array("If-Unmodified-Since-Version: " . ($libraryVersion - 1))
- );
- $this->assert412($response);
-
- // Delete
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag=" . implode("%20||%20", array_merge($tags1, $tags2)),
- array("If-Unmodified-Since-Version: $libraryVersion")
- );
- $this->assert204($response);
-
- // Make sure they're gone
- $response = API::userGet(
- self::$config['userID'],
- "tags?tag=" . implode("%20||%20", array_merge($tags1, $tags2, $tags3))
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- }
-
-
- public function test_deleting_a_tag_should_update_a_linked_item() {
- $tags = ["a", "aa", "b"];
-
- $itemKey = API::createItem("book", [
- "tags" => array_map(function ($tag) {
- return ["tag" => $tag];
- }, $tags)
- ], $this, 'key');
-
- $libraryVersion = API::getLibraryVersion();
-
- // Make sure they're on the item
- $json = API::getItem($itemKey, $this, 'json');
- $this->assertEquals($tags, array_map(function ($tag) { return $tag['tag']; }, $json['data']['tags']));
-
- // Delete
- $response = API::userDelete(
- self::$config['userID'],
- "tags?tag={$tags[0]}",
- ["If-Unmodified-Since-Version: $libraryVersion"]
- );
- $this->assert204($response);
-
- // Make sure they're gone from the item
- $response = API::userGet(
- self::$config['userID'],
- "items?since=$libraryVersion"
- );
- $this->assert200($response);
- $this->assertNumResults(1, $response);
- $json = API::getJSONFromResponse($response);
- $this->assertEquals(
- array_map(function ($tag) { return $tag['tag']; }, $json[0]['data']['tags']),
- array_slice($tags, 1)
- );
- }
-
-
- /**
- * When modifying a tag on an item, only the item itself should have its
- * version updated, not other items that had (and still have) the same tag
- */
- public function testTagAddItemVersionChange() {
- $data1 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "b")
- )
- ), $this, 'jsonData');
- $version1 = $data1['version'];
-
- $data2 = API::createItem("book", array(
- "tags" => array(
- array("tag" => "a"),
- array("tag" => "c")
- )
- ), $this, 'jsonData');
- $version2 = $data2['version'];
-
- // Remove tag 'a' from item 1
- $json1['tags'] = array(
- array("tag" => "d"),
- array("tag" => "c")
- );
-
- $response = API::postItem($data1);
- $this->assert200($response);
-
- // Item 1 version should be one greater than last update
- $json1 = API::getItem($data1['key'], $this, 'json');
- $this->assertEquals($version2 + 1, $json1['version']);
-
- // Item 2 version shouldn't have changed
- $json2 = API::getItem($data2['key'], $this, 'json');
- $this->assertEquals($version2, $json2['version']);
- }
-
-
- public function test_should_change_case_of_existing_tag() {
- $data1 = API::createItem("book", [
- "tags" => [
- ["tag" => "a"],
- ]
- ], $this, 'jsonData');
- $data2 = API::createItem("book", [
- "tags" => [
- ["tag" => "a"]
- ]
- ], $this, 'jsonData');
- $version = $data1['version'];
-
- // Change tag case on one item
- $data1['tags'] = [
- ["tag" => "A"],
- ];
-
- $response = API::postItem($data1);
- $this->assert200($response);
- $this->assert200ForObject($response);
-
- // Item version should be one greater than last update
- $data1 = API::getItem($data1['key'], $this, 'json')['data'];
- $data2 = API::getItem($data2['key'], $this, 'json')['data'];
- $this->assertEquals($version + 1, $data2['version']);
- $this->assertCount(1, $data1['tags']);
- $this->assertContains(["tag" => "A"], $data1['tags']);
- $this->assertContains(["tag" => "a"], $data2['tags']);
- }
-
-
- public function testTagDiacritics() {
- $data = API::createItem("book", [
- "tags" => [
- ["tag" => "ëtest"],
- ]
- ], $this, 'jsonData');
- $version = $data['version'];
-
- // Add 'etest', without accent
- $data['tags'] = [
- ["tag" => "ëtest"],
- ["tag" => "etest"],
- ];
-
- $response = API::postItem($data);
- $this->assert200($response);
- $this->assert200ForObject($response);
-
- // Item version should be one greater than last update
- $data = API::getItem($data['key'], $this, 'json')['data'];
- $this->assertEquals($version + 1, $data['version']);
- $this->assertCount(2, $data['tags']);
- $this->assertContains(["tag" => "ëtest"], $data['tags']);
- $this->assertContains(["tag" => "etest"], $data['tags']);
- }
-}
diff --git a/tests/remote/tests/API/3/TranslationTest.php b/tests/remote/tests/API/3/TranslationTest.php
deleted file mode 100644
index abf8d373..00000000
--- a/tests/remote/tests/API/3/TranslationTest.php
+++ /dev/null
@@ -1,185 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class TranslationTests extends APITests {
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testWebTranslationSingle() {
- $title = 'Zotero: A Guide for Librarians, Researchers and Educators';
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([
- "url" => "http://www.amazon.com/Zotero-Guide-Librarians-Researchers-Educators/dp/0838985890/"
- ]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $itemKey = $json['success'][0];
- $data = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertEquals($title, $data['title']);
- }
-
- /**
- * @group failing-translation
- */
- /*public function testWebTranslationSingleWithChildItems() {
- $title = 'A Clustering Approach to Identify Intergenic Non-coding RNA in Mouse Macrophages';
-
- $response = API::userPost(
- self::$config['userID'],
- "items",
- json_encode([
- "url" => "http://www.computer.org/csdl/proceedings/bibe/2010/4083/00/4083a001-abs.html"
- ]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- $this->assert200ForObject($response, false, 0);
- $this->assert200ForObject($response, false, 1);
- $json = API::getJSONFromResponse($response);
-
- // Check item
- $itemKey = $json['success'][0];
- $data = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertEquals($title, $data['title']);
- // NOTE: Tags currently not served via BibTeX (though available in RIS)
- $this->assertCount(0, $data['tags']);
- //$this->assertContains(['tag' => 'chip-seq; clustering; non-coding rna; rna polymerase; macrophage', 'type' => 1], $data['tags']); // TODO: split in translator
-
- // Check note
- $itemKey = $json['success'][1];
- $data = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertEquals("Complete PDF document was either not available or accessible. "
- . "Please make sure you're logged in to the digital library to retrieve the "
- . "complete PDF document.", $data['note']);
- }*/
-
- /**
- * @group failing-translation
- */
- public function testWebTranslationMultiple() {
- $title = 'Zotero: A guide for librarians, researchers, and educators, Second Edition';
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians"
- ]),
- array("Content-Type: application/json")
- );
- $this->assert300($response);
- $json = json_decode($response->getBody());
-
- $results = get_object_vars($json->items);
- $key = array_keys($results)[0];
- $val = array_values($results)[0];
- $this->assertEquals('0', $key);
- $this->assertEquals($title, $val);
-
- $items = new \stdClass;
- $items->$key = $val;
-
- // Missing token
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians",
- "items" => $items
- ]),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Token not provided with selected items");
-
- // Invalid selection
- $items2 = clone $items;
- $invalidKey = "12345";
- $items2->$invalidKey = $items2->$key;
- unset($items2->$key);
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians",
- "token" => $json->token,
- "items" => $items2
- ]),
- array("Content-Type: application/json")
- );
- $this->assert400($response, "Index '$invalidKey' not found for URL and token");
-
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians",
- "token" => $json->token,
- "items" => $items
- ]),
- array("Content-Type: application/json")
- );
-
- $this->assert200($response);
- $this->assert200ForObject($response);
- $json = API::getJSONFromResponse($response);
- $itemKey = $json['success'][0];
- $data = API::getItem($itemKey, $this, 'json')['data'];
- $this->assertEquals($title, $data['title']);
- }
-
-
- public function testWebTranslationInvalidToken() {
- $response = API::userPost(
- self::$config['userID'],
- "items?key=" . self::$config['apiKey'],
- json_encode([
- "url" => "http://www.amazon.com/s/field-keywords=zotero+guide+librarians",
- "token" => md5(uniqid())
- ]),
- ["Content-Type: application/json"]
- );
- $this->assert400($response, "'token' is valid only for item selection requests");
- }
-}
diff --git a/tests/remote/tests/API/3/VersionTest.php b/tests/remote/tests/API/3/VersionTest.php
deleted file mode 100644
index 135dfcec..00000000
--- a/tests/remote/tests/API/3/VersionTest.php
+++ /dev/null
@@ -1,1148 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2013 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-namespace APIv3;
-use API3 as API;
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-
-class VersionTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
- API::userClear(self::$config['userID']);
- }
-
-
- public function testSingleObjectLastModifiedVersion() {
- $this->_testSingleObjectLastModifiedVersion('collection');
- $this->_testSingleObjectLastModifiedVersion('item');
- $this->_testSingleObjectLastModifiedVersion('search');
- }
-
-
- public function testMultiObjectLastModifiedVersion() {
- $this->_testMultiObjectLastModifiedVersion('collection');
- $this->_testMultiObjectLastModifiedVersion('item');
- $this->_testMultiObjectLastModifiedVersion('search');
- }
-
-
- public function testMultiObject304NotModified() {
- $this->_testMultiObject304NotModified('collection');
- $this->_testMultiObject304NotModified('item');
- $this->_testMultiObject304NotModified('search');
- $this->_testMultiObject304NotModified('setting');
- $this->_testMultiObject304NotModified('tag');
- }
-
-
- public function testSinceAndVersionsFormat() {
- $this->_testSinceAndVersionsFormat('collection', 'since');
- $this->_testSinceAndVersionsFormat('item', 'since');
- $this->_testSinceAndVersionsFormat('search', 'since');
- API::userClear(self::$config['userID']);
- $this->_testSinceAndVersionsFormat('collection', 'newer');
- $this->_testSinceAndVersionsFormat('item', 'newer');
- $this->_testSinceAndVersionsFormat('search', 'newer');
- }
-
-
- public function testUploadUnmodified() {
- $this->_testUploadUnmodified('collection');
- $this->_testUploadUnmodified('item');
- $this->_testUploadUnmodified('search');
- }
-
-
- public function testTagsSince() {
- self::_testTagsSince('since');
- API::userClear(self::$config['userID']);
- self::_testTagsSince('newer');
- }
-
-
-
- public function test_should_not_include_library_version_for_400() {
- $json = API::createItem("book", [], $this, 'json');
- $libraryVersion = $json['version'];
- $response = API::userPut(
- self::$config['userID'],
- "items/" . $json['key'],
- json_encode($json),
- [
- "Content-Type: application/json",
- // 400 due to version property mismatch
- "If-Unmodified-Since-Version: " . ($json['version'] - 1)
- ]
- );
- $this->assert400($response);
- $this->assertNull($response->getHeader('Last-Modified-Version'));
- }
-
- public function test_should_include_library_version_for_412() {
- $json = API::createItem("book", [], $this, 'json');
- $libraryVersion = $json['version'];
- $json['data']['version']--;
- $response = API::userPut(
- self::$config['userID'],
- "items/" . $json['key'],
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: " . ($json['version'] - 1)
- ]
- );
- $this->assert412($response);
- $this->assertLastModifiedVersion($libraryVersion, $response);
- }
-
-
- private function _testSingleObjectLastModifiedVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
- $keyProp = $objectType . "Key";
- $versionProp = $objectType . "Version";
-
- switch ($objectType) {
- case 'collection':
- $objectKey = API::createCollection("Name", false, $this, 'key');
- break;
-
- case 'item':
- $objectKey = API::createItem("book", array("title" => "Title"), $this, 'key');
- break;
-
- case 'search':
- $objectKey = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'key'
- );
- break;
- }
-
- // JSON: Make sure all three instances of the object version
- // (Last-Modified-Version, 'version', and data.version)
- // match the library version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert200($response);
- $objectVersion = $response->getHeader("Last-Modified-Version");
- $json = API::getJSONFromResponse($response);
- $this->assertEquals($objectVersion, $json['version']);
- $this->assertEquals($objectVersion, $json['data']['version']);
-
- // Atom: Make sure all three instances of the object version
- // (Last-Modified-Version, zapi:version, and the JSON
- // {$objectType}Version property match the library version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?content=json"
- );
- $this->assert200($response);
- $objectVersion = $response->getHeader("Last-Modified-Version");
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals($objectVersion, $json['version']);
- $this->assertEquals($objectVersion, $data['version']);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?limit=1"
- );
- $this->assert200($response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $this->assertEquals($libraryVersion, $objectVersion);
-
- $this->_modifyJSONObject($objectType, $json);
-
- // No If-Unmodified-Since-Version or JSON version property
- unset($json['version']);
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json)
- );
- $this->assert428($response);
-
- // Out of date version
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- array(
- "If-Unmodified-Since-Version: " . ($objectVersion - 1)
- )
- );
- $this->assert412($response);
-
- // Update with version header
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json),
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert204($response);
- $newObjectVersion = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($objectVersion, $newObjectVersion);
-
- // Update object with JSON version property
- $this->_modifyJSONObject($objectType, $json);
- $json['version'] = $newObjectVersion;
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- json_encode($json)
- );
- $this->assert204($response);
- $newObjectVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertGreaterThan($newObjectVersion, $newObjectVersion2);
-
- // Make sure new library version matches new object version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?limit=1"
- );
- $this->assert200($response);
- $newLibraryVersion = $response->getHeader("Last-Modified-Version");
- $this->assertEquals($newObjectVersion2, $newLibraryVersion);
- return;
-
- // Create an item to increase the library version, and make sure
- // original object version stays the same
- API::createItem("book", array("title" => "Title"), $this, 'key');
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey?limit=1"
- );
- $this->assert200($response);
- $newObjectVersion2 = $response->getHeader("Last-Modified-Version");
- $this->assertEquals($newLibraryVersion, $newObjectVersion2);
-
- //
- // Delete object
- //
-
- // No If-Unmodified-Since-Version
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert428($response);
-
- // Outdated If-Unmodified-Since-Version
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- array(
- "If-Unmodified-Since-Version: " . $objectVersion
- )
- );
- $this->assert412($response);
-
- // Delete object
- $response = API::userDelete(
- self::$config['userID'],
- "$objectTypePlural/$objectKey",
- array(
- "If-Unmodified-Since-Version: " . $newObjectVersion2
- )
- );
- $this->assert204($response);
- }
-
-
- private function _modifyJSONObject($objectType, &$json) {
- // Modifying object should increase its version
- switch ($objectType) {
- case 'collection':
- $json['name'] = "New Name " . uniqid();
- break;
-
- case 'item':
- $json['title'] = "New Title" . uniqid();
- break;
-
- case 'search':
- $json['name'] = "New Name" . uniqid();
- break;
- }
- }
-
-
- private function _testMultiObjectLastModifiedVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?limit=1"
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
-
- switch ($objectType) {
- case 'collection':
- $json = new \stdClass();
- $json->name = "Name";
- break;
-
- case 'item':
- $json = API::getItemTemplate("book");
- break;
-
- case 'search':
- $json = new \stdClass();
- $json->name = "Name";
- $json->conditions = array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- );
- break;
- }
-
- // Outdated library version
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode(array(
- $objectTypePlural => array($json)
- )),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: " . ($version - 1)
- )
- );
- $this->assert412($response);
-
- // Make sure version didn't change during failure
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?limit=1"
- );
- $this->assertEquals($version, $response->getHeader("Last-Modified-Version"));
-
- // Create a new object, using library timestamp
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array(
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: $version"
- )
- );
- $this->assert200($response);
- $version2 = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version2));
- // Version should be incremented on new object
- $this->assertGreaterThan($version, $version2);
- $objectKey = API::getFirstSuccessKeyFromResponse($response);
-
- // Check single-object request
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $this->assert200($response);
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version2);
- $json = API::getJSONFromResponse($response)['data'];
-
- // Modify object
- $json['key'] = $objectKey;
- switch ($objectType) {
- case 'collection':
- $json['name'] = "New Name";
- break;
-
- case 'item':
- $json['title'] = "New Title";
- break;
-
- case 'search':
- $json['name'] = "New Name";
- break;
- }
-
- // No If-Unmodified-Since-Version or object version property
- unset($json['version']);
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert428ForObject($response);
-
- // Outdated object version property
- $json['version'] = $version - 1;
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array(
- "Content-Type: application/json"
- )
- );
- $this->assert412ForObject($response, ucwords($objectType)
- . " has been modified since specified version "
- . "(expected {$json['version']}, found $version)");
-
- // Modify object, using object version property
- $json['version'] = $version;
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert200($response);
- // Version should be incremented on modified object
- $version3 = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version3));
- $this->assertGreaterThan($version2, $version3);
-
- // Check library version
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural"
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version3);
-
- // Check single-object request
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural/$objectKey"
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
- $this->assertEquals($version, $version3);
-
- // TODO: Version should be incremented on deleted item
- }
-
-
- //
- // PATCH (single object)
- //
-
- // PATCH to a missing object without a version is a 404
- public function testPatchMissingObjectWithoutVersion() {
- $this->_testPatchMissingObjectWithoutVersion('collection');
- $this->_testPatchMissingObjectWithoutVersion('item');
- $this->_testPatchMissingObjectWithoutVersion('search');
- }
-
- private function _testPatchMissingObjectWithoutVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/TPMBJWNV",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert404($response);
- }
-
-
- // PATCH to an existing object without a version is a 428 Precondition Required
- public function testPatchExistingObjectWithoutVersion() {
- //$this->_testPatchExistingObjectWithoutVersion('collection');
- //$this->_testPatchExistingObjectWithoutVersion('item');
- $this->_testPatchExistingObjectWithoutVersion('search');
- }
-
- private function _testPatchExistingObjectWithoutVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert428($response);
- }
-
-
- // PATCH with version header > 0 to a missing object is a 404
- public function testPatchMissingObjectWithVersionHeader() {
- $this->_testPatchMissingObjectWithVersionHeader('collection');
- $this->_testPatchMissingObjectWithVersionHeader('item');
- $this->_testPatchMissingObjectWithVersionHeader('search');
- }
-
- private function _testPatchMissingObjectWithVersionHeader($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/TPMBJWVH",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 123"
- ]
- );
- $this->assert404($response);
- }
-
-
- // PATCH with version property > 0 to a missing object is a 404
- public function testPatchMissingObjectWithVersionProperty() {
- $this->_testPatchMissingObjectWithVersionProperty('collection');
- $this->_testPatchMissingObjectWithVersionProperty('item');
- $this->_testPatchMissingObjectWithVersionProperty('search');
- }
-
- private function _testPatchMissingObjectWithVersionProperty($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
- $json['version'] = 123;
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/TPMBJWVP",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert404($response);
- }
-
-
- // PATCH to a missing object with version 0 header is a 204
- public function testPatchMissingObjectWithVersion0Header() {
- $this->_testPatchMissingObjectWithVersion0Header('collection');
- $this->_testPatchMissingObjectWithVersion0Header('item');
- $this->_testPatchMissingObjectWithVersion0Header('search');
- }
-
- private function _testPatchMissingObjectWithVersion0Header($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/TPMBWVZH",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- ]
- );
- $this->assert204($response);
- }
-
-
- // PATCH to a missing object with version 0 property is a 204
- public function testPatchMissingObjectWithVersion0Property() {
- $this->_testPatchMissingObjectWithVersion0Property('collection');
- $this->_testPatchMissingObjectWithVersion0Property('item');
- $this->_testPatchMissingObjectWithVersion0Property('search');
- }
-
- private function _testPatchMissingObjectWithVersion0Property($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
- $json['version'] = 0;
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/TPMBWVZP",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert204($response);
- }
-
-
- // PATCH to an existing object with version header 0 is 412
- public function testPatchExistingObjectWithVersion0Header() {
- $this->_testPatchExistingObjectWithVersion0Header('collection');
- $this->_testPatchExistingObjectWithVersion0Header('item');
- $this->_testPatchExistingObjectWithVersion0Header('search');
- }
-
- private function _testPatchExistingObjectWithVersion0Header($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- ]
- );
- $this->assert412($response);
- }
-
-
- // PATCH to an existing object with version property 0 is 412
- public function testPatchExistingObjectWithVersion0Property() {
- $this->_testPatchExistingObjectWithVersion0Property('collection');
- $this->_testPatchExistingObjectWithVersion0Property('item');
- $this->_testPatchExistingObjectWithVersion0Property('search');
- }
-
- private function _testPatchExistingObjectWithVersion0Property($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
- $json['version'] = 0;
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert412($response);
- }
-
-
- // PATCH to an existing object with version header < current version is 412
- public function testPatchExistingObjectWithOldVersionHeader() {
- $this->_testPatchExistingObjectWithOldVersionHeader('collection');
- $this->_testPatchExistingObjectWithOldVersionHeader('item');
- $this->_testPatchExistingObjectWithOldVersionHeader('search');
- }
-
- private function _testPatchExistingObjectWithOldVersionHeader($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 1"
- ]
- );
- $this->assert412($response);
- }
-
-
- // PATCH to an existing object with version property < current version is 412
- public function testPatchExistingObjectWithOldVersionProperty() {
- $this->_testPatchExistingObjectWithOldVersionProperty('collection');
- $this->_testPatchExistingObjectWithOldVersionProperty('item');
- $this->_testPatchExistingObjectWithOldVersionProperty('search');
- }
-
- private function _testPatchExistingObjectWithOldVersionProperty($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
- $json['version'] = 1;
-
- $response = API::userPatch(
- self::$config['userID'],
- "$objectTypePlural/$key",
- json_encode($json),
- [
- "Content-Type: application/json"
- ]
- );
- $this->assert412($response);
- }
-
-
- //
- // PATCH (multiple objects)
- //
-
- // POST with a version 0 header to an existing library is a 412
- public function testPostExistingLibraryWithVersion0Header() {
- $this->_testPostExistingLibraryWithVersion0Header('collection');
- $this->_testPostExistingLibraryWithVersion0Header('item');
- $this->_testPostExistingLibraryWithVersion0Header('search');
- }
-
- private function _testPostExistingLibraryWithVersion0Header($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
-
- $response = API::userPost(
- self::$config['userID'],
- $objectTypePlural,
- json_encode([$json]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: 0"
- ]
- );
- $this->assert412($response);
- }
-
- // POST to a missing object with a version property of 0 is a 204 for that object
- public function testPatchMissingObjectsWithVersion0Property() {
- $this->_testPatchMissingObjectsWithVersion0Property('collection');
- $this->_testPatchMissingObjectsWithVersion0Property('item');
- $this->_testPatchMissingObjectsWithVersion0Property('search');
- }
-
- private function _testPatchMissingObjectsWithVersion0Property($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = 'TPMSWVZP';
- $json['version'] = 0;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert200ForObject($response);
- }
-
-
- // POST with version > 0 to a missing object is a 404 for that object
- public function testPatchMissingObjectsWithVersion() {
- $this->_testPatchMissingObjectsWithVersion('collection');
- $this->_testPatchMissingObjectsWithVersion('item');
- $this->_testPatchMissingObjectsWithVersion('search');
- }
-
- private function _testPatchMissingObjectsWithVersion($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = 'TPMBJSWV';
- $json['version'] = 123;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert404ForObject($response, ucwords($objectType)
- . " doesn't exist (expected version 123; use 0 instead)");
- }
-
- // POST to an existing object with a version prop of 0 is a 412 for that object
- public function testPatchExistingObjectsWithVersion0Property() {
- $this->_testPatchExistingObjectsWithVersion0Property('collection');
- $this->_testPatchExistingObjectsWithVersion0Property('item');
- $this->_testPatchExistingObjectsWithVersion0Property('search');
- }
-
- private function _testPatchExistingObjectsWithVersion0Property($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = $key;
- $json['version'] = 0;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert412ForObject($response);
- }
-
-
- // POST to an existing object without a version prop but with a header is a 428 for that object
- public function testPatchExistingObjectsWithoutVersionWithHeader() {
- $this->_testPatchExistingObjectsWithoutVersionWithHeader('collection');
- $this->_testPatchExistingObjectsWithoutVersionWithHeader('item');
- $this->_testPatchExistingObjectsWithoutVersionWithHeader('search');
- }
-
- private function _testPatchExistingObjectsWithoutVersionWithHeader($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $existing = API::createDataObject($objectType, 'json');
- $key = $existing['key'];
- $libraryVersion = $existing['version'];
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = $key;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert428ForObject($response);
- }
-
-
- // POST to an existing object without a version prop and without a header is a 428 for that object
- public function testPatchExistingObjectsWithoutVersionWithoutHeader() {
- $this->_testPatchExistingObjectsWithoutVersionWithoutHeader('collection');
- $this->_testPatchExistingObjectsWithoutVersionWithoutHeader('item');
- $this->_testPatchExistingObjectsWithoutVersionWithoutHeader('search');
- }
-
- private function _testPatchExistingObjectsWithoutVersionWithoutHeader($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = $key;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert428ForObject($response);
- }
-
-
- // should return 412 for POST to /settings with outdated version header
- public function testPostToSettingsWithOutdatedVersionHeader() {
- $libraryVersion = API::getLibraryVersion();
-
- // Outdated library version
- $response = API::userPost(
- self::$config['userID'],
- "settings",
- json_encode([]),
- [
- "Content-Type: application/json",
- "If-Unmodified-Since-Version: " . ($libraryVersion - 1)
- ]
- );
- $this->assert412($response);
- }
-
-
- // POST to an existing object with a version prop < current version is a 412 for that object
- public function testPatchExistingObjectsWithOldVersion0Property() {
- $this->_testPatchExistingObjectsWithOldVersionProperty('collection');
- $this->_testPatchExistingObjectsWithOldVersionProperty('item');
- $this->_testPatchExistingObjectsWithOldVersionProperty('search');
- }
-
- private function _testPatchExistingObjectsWithOldVersionProperty($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $key = API::createDataObject($objectType, 'key');
- $json = API::createUnsavedDataObject($objectType);
- $json['key'] = $key;
- $json['version'] = 1;
-
- $response = API::userPost(
- self::$config['userID'],
- "$objectTypePlural",
- json_encode([$json]),
- array("Content-Type: application/json")
- );
- $this->assert412ForObject($response);
- }
-
-
-
- private function _testMultiObject304NotModified($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural"
- );
- $version = $response->getHeader("Last-Modified-Version");
- $this->assertTrue(is_numeric($version));
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural",
- array(
- "If-Modified-Since-Version: $version"
- )
- );
- $this->assert304($response);
- }
-
-
- private function _testSinceAndVersionsFormat($objectType, $sinceParam) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- $dataArray = [];
-
- switch ($objectType) {
- case 'collection':
- $dataArray[] = API::createCollection("Name", false, $this, 'jsonData');
- $dataArray[] = API::createCollection("Name", false, $this, 'jsonData');
- $dataArray[] = API::createCollection("Name", false, $this, 'jsonData');
- break;
-
- case 'item':
- $dataArray[] = API::createItem("book", array("title" => "Title"), $this, 'jsonData');
- $dataArray[] = API::createNoteItem("Foo", $dataArray[0]['key'], $this, 'jsonData');
- $dataArray[] = API::createItem("book", array("title" => "Title"), $this, 'jsonData');
- $dataArray[] = API::createItem("book", array("title" => "Title"), $this, 'jsonData');
- break;
-
-
- case 'search':
- $dataArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'jsonData'
- );
- $dataArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'jsonData'
- );
- $dataArray[] = API::createSearch(
- "Name",
- array(
- array(
- "condition" => "title",
- "operator" => "contains",
- "value" => "test"
- )
- ),
- $this,
- 'jsonData'
- );
- }
-
- $objects = $dataArray;
-
- $firstVersion = $objects[0]['version'];
-
- $response = API::userGet(
- self::$config['userID'],
- "$objectTypePlural?format=versions&$sinceParam=$firstVersion"
- );
-
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertCount(sizeOf($objects) - 1, $json);
- $keys = array_keys($json);
-
- if ($objectType == 'item') {
- $this->assertEquals($objects[3]['key'], array_shift($keys));
- $this->assertEquals($objects[3]['version'], array_shift($json));
- }
- $this->assertEquals($objects[2]['key'], array_shift($keys));
- $this->assertEquals($objects[2]['version'], array_shift($json));
- $this->assertEquals($objects[1]['key'], array_shift($keys));
- $this->assertEquals($objects[1]['version'], array_shift($json));
- $this->assertEmpty($json);
-
- // Test /top for items
- if ($objectType == 'item') {
- $response = API::userGet(
- self::$config['userID'],
- "items/top?format=versions&$sinceParam=$firstVersion"
- );
-
- $this->assert200($response);
- $json = json_decode($response->getBody(), true);
- $this->assertNotNull($json);
- $this->assertCount(sizeOf($objects) - 2, $json); // Exclude first item and child
- $keys = array_keys($json);
-
- $objects = $dataArray;
-
- $this->assertEquals($objects[3]['key'], array_shift($keys));
- $this->assertEquals($objects[3]['version'], array_shift($json));
- $this->assertEquals($objects[2]['key'], array_shift($keys));
- $this->assertEquals($objects[2]['version'], array_shift($json));
- $this->assertEmpty($json);
- }
- }
-
- private function _testUploadUnmodified($objectType) {
- $objectTypePlural = API::getPluralObjectType($objectType);
-
- switch ($objectType) {
- case 'collection':
- $data = API::createCollection("Name", false, $this, 'jsonData');
- break;
-
- case 'item':
- $data = API::createItem("book", array("title" => "Title"), $this, 'jsonData');
- break;
-
- case 'search':
- $data = API::createSearch("Name", 'default', $this, 'jsonData');
- break;
- }
-
- $version = $data['version'];
- $this->assertNotEquals(0, $version);
-
- $response = API::userPut(
- self::$config['userID'],
- "$objectTypePlural/{$data['key']}",
- json_encode($data)
- );
- $this->assert204($response);
- $this->assertEquals($version, $response->getHeader("Last-Modified-Version"));
-
- switch ($objectType) {
- case 'collection':
- $json = API::getCollection($data['key'], $this, 'json');
- break;
-
- case 'item':
- $json = API::getItem($data['key'], $this, 'json');
- break;
-
- case 'search':
- $json = API::getSearch($data['key'], $this, 'json');
- break;
- }
- $this->assertEquals($version, $json['version']);
- }
-
-
- private function _testTagsSince($param) {
- $tags1 = array("a", "aa", "b");
- $tags2 = array("b", "c", "cc");
-
- $data1 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags1)
- ), $this, 'jsonData');
-
- $data2 = API::createItem("book", array(
- "tags" => array_map(function ($tag) {
- return array("tag" => $tag);
- }, $tags2)
- ), $this, 'jsonData');
-
- // Only newly added tags should be included in 'since',
- // not previously added tags or tags added to items
- $response = API::userGet(
- self::$config['userID'],
- "tags?$param=" . $data1['version']
- );
- $this->assertNumResults(2, $response);
-
- // Deleting an item shouldn't update associated tag versions
- $response = API::userDelete(
- self::$config['userID'],
- "items/{$data1['key']}",
- array(
- "If-Unmodified-Since-Version: " . $data1['version']
- )
- );
- $this->assert204($response);
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?$param=" . $data1['version']
- );
- $this->assertNumResults(2, $response);
- $libraryVersion = $response->getHeader("Last-Modified-Version");
-
- $response = API::userGet(
- self::$config['userID'],
- "tags?$param=" . $libraryVersion
- );
- $this->assertNumResults(0, $response);
- }
-}
diff --git a/tests/remote/tests/API/APITests.inc.php b/tests/remote/tests/API/APITests.inc.php
deleted file mode 100644
index 5f1d234a..00000000
--- a/tests/remote/tests/API/APITests.inc.php
+++ /dev/null
@@ -1,92 +0,0 @@
-
-/*
- ***** BEGIN LICENSE BLOCK *****
-
- This file is part of the Zotero Data Server.
-
- Copyright © 2012 Center for History and New Media
- George Mason University, Fairfax, Virginia, USA
- http://zotero.org
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see .
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once 'include/bootstrap.inc.php';
-
-//
-// Helper functions
-//
-class APITests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $nsZAPI;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
- }
-
-
- public function setUp() {
- }
-
-
- public function test() {}
-
-
- protected function assertContentType($contentType, $response) {
- try {
- $this->assertEquals($contentType, $response->getHeader("Content-Type"));
- }
- catch (Exception $e) {
- echo "\n" . $response->getBody() . "\n";
- throw ($e);
- }
- }
-
-
- protected function assertHTTPStatus($status, $response) {
- try {
- $this->assertEquals($status, $response->getStatus());
- }
- catch (Exception $e) {
- echo "\n" . $response->getBody() . "\n";
- throw ($e);
- }
- }
-
-
- protected function assertCompression($response) {
- $this->assertEquals('gzip', $response->getHeader('Content-Encoding'));
- }
-
-
- protected function assertNoCompression($response) {
- $this->assertNull($response->getHeader('Content-Encoding'));
- }
-
-
- protected function assertContentLength($length, $response) {
- $this->assertEquals($length, $response->getHeader('Content-Length'));
- }
-
-
- protected function assertISO8601Date($date) {
- $this->assertTrue(\Zotero_Date::isISO8601($date));
- }
-}
-
diff --git a/tests/remote/tests/API/GeneralTest.php b/tests/remote/tests/API/GeneralTest.php
deleted file mode 100644
index 0d4552b7..00000000
--- a/tests/remote/tests/API/GeneralTest.php
+++ /dev/null
@@ -1,238 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-require_once 'APITests.inc.php';
-require_once 'include/api3.inc.php';
-use API3 as API;
-
-class GeneralTests extends APITests {
- public static function setUpBeforeClass() {
- parent::setUpBeforeClass();
- API::userClear(self::$config['userID']);
- }
-
- public static function tearDownAfterClass() {
- parent::tearDownAfterClass();
- API::userClear(self::$config['userID']);
- }
-
- public function setUp() {
- parent::setUp();
- API::useAPIKey(self::$config['apiKey']);
- API::useAPIVersion(false);
- }
-
- public function testAPIVersionHeader() {
- $minVersion = 1;
- $maxVersion = 3;
- $defaultVersion = 3;
-
- for ($i = $minVersion; $i <= $maxVersion; $i++) {
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&limit=1",
- [
- "Zotero-API-Version: $i"
- ]
- );
- $this->assertEquals($i, $response->getHeader("Zotero-API-Version"));
- }
-
- // Default
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&limit=1"
- );
- $this->assertEquals($defaultVersion, $response->getHeader("Zotero-API-Version"));
- }
-
-
- public function testAPIVersionParameter() {
- $minVersion = 1;
- $maxVersion = 3;
-
- for ($i = $minVersion; $i <= $maxVersion; $i++) {
- $response = API::userGet(
- self::$config['userID'],
- "items?format=keys&limit=1&v=$i"
- );
- $this->assertEquals($i, $response->getHeader("Zotero-API-Version"));
- }
- }
-
-
- public function testAuthorization() {
- $apiKey = self::$config['apiKey'];
- API::useAPIKey(false);
-
- // Zotero-API-Key header
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- "Zotero-API-Key: $apiKey"
- ]
- );
- $this->assertHTTPStatus(200, $response);
-
- // Authorization header
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- "Authorization: Bearer $apiKey"
- ]
- );
- $this->assertHTTPStatus(200, $response);
-
- // Query parameter
- $response = API::userGet(
- self::$config['userID'],
- "items?key=$apiKey"
- );
- $this->assertHTTPStatus(200, $response);
-
- // Zotero-API-Key header and query parameter
- $response = API::userGet(
- self::$config['userID'],
- "items?key=$apiKey",
- [
- "Zotero-API-Key: $apiKey"
- ]
- );
- $this->assertHTTPStatus(200, $response);
-
- // No key
- $response = API::userGet(
- self::$config['userID'],
- "items"
- );
- $this->assertHTTPStatus(403, $response);
-
- // Zotero-API-Key header and empty key (which is still an error)
- $response = API::userGet(
- self::$config['userID'],
- "items?key=",
- [
- "Zotero-API-Key: $apiKey"
- ]
- );
- $this->assertHTTPStatus(400, $response);
-
- // Zotero-API-Key header and incorrect Authorization key (which is ignored)
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- "Zotero-API-Key: $apiKey",
- "Authorization: Bearer invalidkey"
- ]
- );
- $this->assertHTTPStatus(200, $response);
-
- // Zotero-API-Key header and key mismatch
- $response = API::userGet(
- self::$config['userID'],
- "items?key=invalidkey",
- [
- "Zotero-API-Key: $apiKey"
- ]
- );
- $this->assertHTTPStatus(400, $response);
-
- // Invalid Bearer format
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- "Authorization: Bearer key=$apiKey"
- ]
- );
- $this->assertHTTPStatus(400, $response);
-
- // Ignored OAuth 1.0 header, with key query parameter
- $response = API::userGet(
- self::$config['userID'],
- "items?key=$apiKey",
- [
- 'Authorization: OAuth oauth_consumer_key="aaaaaaaaaaaaaaaaaaaa"'
- ]
- );
- $this->assertHTTPStatus(200, $response);
-
- // Ignored OAuth 1.0 header, with no key query parameter
- $response = API::userGet(
- self::$config['userID'],
- "items",
- [
- 'Authorization: OAuth oauth_consumer_key="aaaaaaaaaaaaaaaaaaaa"'
- ]
- );
- $this->assertHTTPStatus(403, $response);
- }
-
-
- public function testCORS() {
- $response = HTTP::options(
- self::$config['apiURLPrefix'],
- [
- 'Origin: http://example.com'
- ]
- );
- $this->assertHTTPStatus(200, $response);
- $this->assertEquals('', $response->getBody());
- $this->assertEquals('*', $response->getHeader('Access-Control-Allow-Origin'));
- }
-
-
- public function test200Compression() {
- $response = API::get("itemTypes");
- $this->assertHTTPStatus(200, $response);
- $this->assertCompression($response);
- }
-
-
- public function test404Compression() {
- $response = API::get("invalidurl");
- $this->assertHTTPStatus(404, $response);
- $this->assertCompression($response);
- }
-
-
- public function test204NoCompression() {
- $json = API::createItem("book", [], null, 'jsonData');
- $response = API::userDelete(
- self::$config['userID'],
- "items/{$json['key']}",
- [
- "If-Unmodified-Since-Version: {$json['version']}"
- ]
- );
- $this->assertHTTPStatus(204, $response);
- $this->assertNoCompression($response);
- $this->assertContentLength(0, $response);
- }
-}
diff --git a/tests/remote/tests/Sync/CollectionTest.php b/tests/remote/tests/Sync/CollectionTest.php
deleted file mode 100644
index 35c48e01..00000000
--- a/tests/remote/tests/Sync/CollectionTest.php
+++ /dev/null
@@ -1,144 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-use API2 as API;
-require_once 'include/bootstrap.inc.php';
-require_once 'include/api2.inc.php';
-require_once 'include/sync.inc.php';
-
-class SyncCollectionTests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $sessionID;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- API::useAPIVersion(2);
- }
-
-
- public function setUp() {
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- API::groupClear(self::$config['ownedPublicGroupID']);
- self::$sessionID = Sync::login();
- }
-
-
- public function tearDown() {
- Sync::logout(self::$sessionID);
- self::$sessionID = null;
- }
-
-
- public function testCollectionItemUpdate() {
- $collectionKey = Sync::createCollection(
- self::$sessionID, self::$config['libraryID'], "Test", null, $this
- );
- $itemKey = Sync::createItem(
- self::$sessionID, self::$config['libraryID'], "book", null, $this
- );
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- // Get the item version
- $itemXML = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($itemXML);
- $json = json_decode($data['content'], true);
- $itemVersion = $json['itemVersion'];
- $this->assertNotNull($itemVersion);
-
- // Add via sync
- $collectionXML = $xml->updated[0]->collections[0]->collection[0];
- $collectionXML['libraryID'] = self::$config['libraryID'];
- $collectionXML->addChild("items", $itemKey);
-
- $data = ''
- . $collectionXML->asXML()
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $data, true);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Make sure item was updated
- $itemXML = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($itemXML);
- $json = json_decode($data['content'], true);
- $this->assertGreaterThan($itemVersion, $json['itemVersion']);
- $itemVersion = $json['itemVersion'];
- $this->assertCount(1, $json['collections']);
- $this->assertContains($collectionKey, $json['collections']);
-
- // Remove via sync
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $collectionXML = $xml->updated[0]->collections[0]->collection[0];
- $collectionXML['libraryID'] = self::$config['libraryID'];
- unset($collectionXML->items);
-
- $data = ''
- . $collectionXML->asXML()
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $data, true);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Make sure item was removed
- $itemXML = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($itemXML);
- $json = json_decode($data['content'], true);
- $this->assertGreaterThan($itemVersion, $json['itemVersion']);
- $this->assertCount(0, $json['collections']);
- }
-
-
- public function testCollectionNameTooLong() {
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $content = str_repeat("1", 256);
-
- // Create too-long note via sync
- $data = ''
- . ' '
- . ' ';
-
- Sync::useZoteroVersion();
- $response = Sync::upload(self::$sessionID, $updateKey, $data, true);
- $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true);
-
- $this->assertTrue(isset($xml->error));
- $this->assertEquals("COLLECTION_TOO_LONG", $xml->error["code"]);
- $this->assertRegExp('/^Collection \'.+\' too long/', (string) $xml->error);
- }
-}
diff --git a/tests/remote/tests/Sync/CreatorTest.php b/tests/remote/tests/Sync/CreatorTest.php
deleted file mode 100644
index 3d15dbea..00000000
--- a/tests/remote/tests/Sync/CreatorTest.php
+++ /dev/null
@@ -1,328 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-use API2 as API;
-require_once 'include/api2.inc.php';
-require_once 'include/sync.inc.php';
-
-class CreatorSyncTests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $sessionID;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- API::useAPIVersion(2);
- }
-
-
- public function setUp() {
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- API::groupClear(self::$config['ownedPublicGroupID']);
- self::$sessionID = Sync::login();
- }
-
-
- public function tearDown() {
- Sync::logout(self::$sessionID);
- self::$sessionID = null;
- }
-
-
- public function testCreatorItemChange() {
- $key = 'AAAAAAAA';
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- // Create item via sync
- $data = '- '
- . '
'
- . ''
- . 'First '
- . 'Last '
- . '0 '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Get item version via API and check creatorSummary
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Last", $creatorSummary);
- $data = API::parseDataFromAtomEntry($xml);
- $version = $data['version'];
-
- // Get item via sync
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $this->assertEquals(1, sizeOf($xml->updated->items->item));
-
- //
- // Modify creator
- //
- $data = ''
- . ''
- . 'First Last '
- . '1 '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Get item via API
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("First Last", $creatorSummary);
- $this->assertTrue(isset($json->creators[0]->name));
- $this->assertEquals("First Last", $json->creators[0]->name);
- $this->assertEquals($version + 1, $data['version']);
- $version = $data['version'];
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- //
- // Modify creator, and include unmodified item
- //
- $data = '- '
- . '
'
- . ' '
- . ''
- . 'Foo '
- . 'Bar '
- . '0 '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Get item via API
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Bar", $creatorSummary);
- $this->assertTrue(isset($json->creators[0]->firstName));
- $this->assertEquals("Foo", $json->creators[0]->firstName);
- $this->assertTrue(isset($json->creators[0]->lastName));
- $this->assertEquals("Bar", $json->creators[0]->lastName);
- $this->assertEquals($version + 1, $data['version']);
- }
-
-
- public function testCreatorItemChangeViaAPI() {
- $key = 'AAAAAAAA';
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- // Create item via sync
- $data = '- '
- . '
'
- . ''
- . 'First '
- . 'Last '
- . '0 '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Get item version via API and check creatorSummary
- API::useAPIVersion(1);
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("Last", $creatorSummary);
- $data = API::parseDataFromAtomEntry($xml);
- $etag = (string) array_get_first($xml->xpath('//atom:entry/atom:content/@zapi:etag'));
- $this->assertNotEquals("", $etag);
-
- // Modify creator
- $json = json_decode($data['content'], true);
- $json['creators'][0] = array(
- "name" => "First Last",
- "creatorType" => "author"
- );
-
- // Modify via API
- $response = API::userPut(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'],
- json_encode($json),
- array("If-Match: $etag")
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("First Last", $creatorSummary);
- $this->assertTrue(isset($json->creators[0]->name));
- $this->assertEquals("First Last", $json->creators[0]->name);
- $newETag = (string) array_get_first($xml->xpath('//atom:entry/zapi:etag'));
- $this->assertNotEquals($etag, $newETag);
-
- // Get item again via API
- $response = API::userGet(
- self::$config['userID'],
- "items/$key?key=" . self::$config['apiKey'] . "&content=json"
- );
- $xml = API::getXMLFromResponse($response);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content']);
-
- $creatorSummary = (string) array_get_first($xml->xpath('//atom:entry/zapi:creatorSummary'));
- $this->assertEquals("First Last", $creatorSummary);
- $this->assertTrue(isset($json->creators[0]->name));
- $this->assertEquals("First Last", $json->creators[0]->name);
- $newETag = (string) array_get_first($xml->xpath('//atom:entry/zapi:etag'));
- $this->assertNotEquals($etag, $newETag);
- }
-
-
- public function testEmptyCreator() {
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- // Create creator via sync
- $data = ''
- . ''
- . ' '
- . '1 '
- . ' '
- . ''
- . '' . chr(0xEF) . chr(0xBB) . chr(0xBF) . ' ' // \uFEFF
- . '1 '
- . ' '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Creators should have been skipped
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $this->assertEquals(0, sizeOf($xml->updated->creators->creator));
-
- // Create creator with valid name
- $data = ''
- . ''
- . 'Test '
- . '1 '
- . ' '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $this->assertEquals(1, sizeOf($xml->updated->creators->creator));
-
- // Update with empty
- $data = ''
- . ''
- . '' . chr(0xEF) . chr(0xBB) . chr(0xBF) . ' '
- . '1 '
- . ' '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $this->assertEquals(1, sizeOf($xml->updated->creators->creator));
- // Not ideal, but for now the updated creator should just be ignored
- $this->assertEquals("Test", (string) $xml->updated->creators->creator->name);
- }
-
-
- public function testEmptyLinkedCreator() {
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- // Create creator via sync
- $data = ''
- . ''
- . '' . chr(0x7f) . ' '
- . '1 '
- . ' '
- . ' '
- . '- '
- . '
'
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Creators should have been skipped
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $this->assertEquals(0, sizeOf($xml->updated->creators->creator));
- }
-}
diff --git a/tests/remote/tests/Sync/FullTextTest.php b/tests/remote/tests/Sync/FullTextTest.php
deleted file mode 100644
index d5a7dc8c..00000000
--- a/tests/remote/tests/Sync/FullTextTest.php
+++ /dev/null
@@ -1,374 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-use API2 as API;
-require_once 'include/api2.inc.php';
-require_once 'include/sync.inc.php';
-
-class SyncFullTextTests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $sessionID;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- API::useAPIVersion(2);
- }
-
- public function setUp() {
- API::userClear(self::$config['userID']);
- self::$sessionID = Sync::login();
- }
-
- public function tearDown() {
- Sync::logout(self::$sessionID);
- self::$sessionID = null;
- }
-
- public function testFullTextSync() {
- $xml = Sync::updated(self::$sessionID);
-
- $updateKey = (string) $xml['updateKey'];
- $key = Zotero_Utilities::randomString(8, 'key', true);
- $dateAdded = date('Y-m-d H:i:s', time() - 1);
- $dateModified = date('Y-m-d H:i:s');
-
- $content = "This is some full-text content.";
- $totalChars = 2500;
-
- $xmlstr = ''
- . ''
- . ' '
- . ' '
- . ''
- . ''
- . htmlspecialchars($content)
- . ' '
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- $xml = Sync::updated(self::$sessionID, 1, false, false, ["ft" => 1]);
- $lastSyncTimestamp = (int) $xml['timestamp'];
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(1, $xml->updated[0]->fulltexts[0]->fulltext->count());
- $this->assertEquals($content, (string) $xml->updated[0]->fulltexts[0]->fulltext[0]);
- $this->assertEquals(strlen($content), (int) $xml->updated[0]->fulltexts[0]->fulltext[0]['indexedChars']);
- $this->assertEquals($totalChars, (int) $xml->updated[0]->fulltexts[0]->fulltext[0]['totalChars']);
-
- $xml = Sync::updated(self::$sessionID, $lastSyncTimestamp + 1, false, false, ["ft" => 1]);
- $this->assertEquals(0, $xml->updated[0]->fulltexts->count());
-
- $xml = Sync::updated(self::$sessionID, 1, false, false, ["ft" => 1]);
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- }
-
- public function testLargeFullTextSync() {
- $xml = Sync::updated(self::$sessionID, 1, false, false, ["ft" => 1]);
- $timestamp1 = (int) $xml['timestamp'];
- $updateKey = (string) $xml['updateKey'];
-
- $key1 = Zotero_Utilities::randomString(8, 'key', true);
- $key2 = Zotero_Utilities::randomString(8, 'key', true);
- $key3 = Zotero_Utilities::randomString(8, 'key', true);
- $key4 = Zotero_Utilities::randomString(8, 'key', true);
-
- $dateAdded = date( 'Y-m-d H:i:s', time() - 1);
- $dateModified = date( 'Y-m-d H:i:s', time());
-
- $content1 = "This is test content";
- $content2 = "This is more test content";
-
- $maxChars = 1000000;
- $str = "abcdf ghijklm ";
- $content3 = str_repeat("abcdf ghijklm ", ceil($maxChars / strlen($str)) + 1);
-
- $content4 = "This is even more test content";
-
- $xmlstr = ''
- . ''
- . ' '
- . ' '
- . ' '
- . ''
- . ''
- . htmlspecialchars($content1)
- . ' '
- . ''
- . htmlspecialchars($content2)
- . ' '
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr);
- $xml = Sync::waitForUpload(self::$sessionID, $response, $this);
- $timestamp2 = (int) $xml['timestamp'];
-
- $xml = Sync::updated(self::$sessionID, $timestamp2, false, false, ["ft" => 1]);
- $updateKey = (string) $xml['updateKey'];
-
- // Wait until the timestamp advances
- do {
- $xml = Sync::updated(self::$sessionID, $timestamp2, false, false, ["ft" => 1]);
- usleep(500);
- }
- while ((int) $xml['timestamp'] <= ($timestamp2 + 2));
-
- $xmlstr = ''
- . ''
- . ' '
- . ' '
- . ' '
- . ''
- . ''
- . htmlspecialchars($content3)
- . ' '
- . ''
- . htmlspecialchars($content4)
- . ' '
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr);
- $xml = Sync::waitForUpload(self::$sessionID, $response, $this);
- $timestamp3 = (int) $xml['timestamp'];
-
- // Get all results
- $xml = Sync::updated(self::$sessionID, 1, false, false, ["ft" => 1]);
-
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(4, $xml->updated[0]->fulltexts[0]->fulltext->count());
-
- $resultContent1 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key1']"));
- $resultContent2 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key2']"));
- $resultContent3 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key3']"));
- $resultContent4 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key4']"));
-
- if ($resultContent3 === "") {
- $this->assertEquals($content1, $resultContent1);
- $this->assertEquals($content2, $resultContent2);
- $this->assertEquals($content4, $resultContent4);
- }
- else {
- $this->assertEquals("", $resultContent1);
- $this->assertEquals("", $resultContent2);
- $this->assertEquals($content3, $resultContent3);
- $this->assertEquals("", $resultContent4);
- }
-
- // Request past last content
- $xml = Sync::updated(self::$sessionID, $timestamp3, false, false, ["ft" => 1]);
- $this->assertEquals(0, $xml->updated[0]->fulltexts->count());
-
- // Request for explicit keys
- $params = ["ft" => 1];
- $params["ftkeys"][self::$config['libraryID']] = [$key1, $key2, $key3, $key4];
- $xml = Sync::updated(self::$sessionID, $timestamp3, false, false, $params);
-
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(4, $xml->updated[0]->fulltexts[0]->fulltext->count());
-
- $resultContent1 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key1']"));
- $resultContent2 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key2']"));
- $resultContent3 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key3']"));
- $resultContent4 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key4']"));
-
- if ($resultContent3 === "") {
- $this->assertEquals($content1, $resultContent1);
- $this->assertEquals($content2, $resultContent2);
- $this->assertEquals($content4, $resultContent4);
- }
- else {
- $this->assertEquals("", $resultContent1);
- $this->assertEquals("", $resultContent2);
- $this->assertEquals($content3, $resultContent3);
- $this->assertEquals("", $resultContent4);
- }
-
- // Request for combo of time and keys
- $params = ["ft" => 1];
- $params["ftkeys"][self::$config['libraryID']] = [$key2];
- $xml = Sync::updated(self::$sessionID, $timestamp2, false, false, $params);
-
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(3, $xml->updated[0]->fulltexts[0]->fulltext->count());
-
- $resultContent2 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key2']"));
- $resultContent3 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key3']"));
- $resultContent4 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key4']"));
-
- if ($resultContent3 === "") {
- $this->assertEquals($content2, $resultContent2);
- $this->assertEquals($content4, $resultContent4);
- }
- else {
- $this->assertEquals("", $resultContent2);
- $this->assertEquals($content3, $resultContent3);
- $this->assertEquals("", $resultContent4);
- }
-
- // Request past last content, again
- $xml = Sync::updated(self::$sessionID, $timestamp3, false, false, ["ft" => 1]);
- $this->assertEquals(0, $xml->updated[0]->fulltexts->count());
-
- // Request for single long content
- $params = ["ft" => 1];
- $params["ftkeys"][self::$config['libraryID']] = [$key3];
- $xml = Sync::updated(self::$sessionID, $timestamp3, false, false, $params);
-
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(1, $xml->updated[0]->fulltexts[0]->fulltext->count());
-
- $resultContent3 = (string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key3']"));
- $this->assertEquals($content3, $resultContent3);
-
- // Request for all items by upgrade flag
- $params = [
- "ft" => 1,
- "ftkeys" => "all"
- ];
- $xml = Sync::updated(self::$sessionID, $timestamp3, false, false, $params);
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(4, $xml->updated[0]->fulltexts[0]->fulltext->count());
-
- // Request for empty items with FT disabled
- $params = ["ft" => 0];
- $xml = Sync::updated(self::$sessionID, 1, false, false, $params);
- $this->assertEquals(1, $xml->updated[0]->fulltexts->count());
- $this->assertEquals(4, $xml->updated[0]->fulltexts[0]->fulltext->count());
- $this->assertEmpty((string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key1']")));
- $this->assertEmpty((string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key2']")));
- $this->assertEmpty((string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key3']")));
- $this->assertEmpty((string) array_get_first($xml->updated[0]->fulltexts[0]->xpath("//fulltext[@key='$key4']")));
- }
-
-
- public function testFullTextNoAccess() {
- API::groupClear(self::$config['ownedPrivateGroupID2']);
-
- // Add item to group as user 2
- $user2SessionID = Sync::login([
- 'username' => self::$config['username2'],
- 'password' => self::$config['password2']
- ]);
- $xml = Sync::updated($user2SessionID);
- $updateKey = (string) $xml['updateKey'];
- $key = Zotero_Utilities::randomString(8, 'key', true);
- $dateAdded = date('Y-m-d H:i:s', time() - 1);
- $dateModified = date('Y-m-d H:i:s');
- $xmlstr = ''
- . ''
- . ' '
- . ' '
- . '';
- $response = Sync::upload($user2SessionID, $updateKey, $xmlstr);
- Sync::waitForUpload($user2SessionID, $response, $this);
-
- // Make sure item exists
- $xml = Sync::updated($user2SessionID, 1);
- $this->assertEquals(1, $xml->updated[0]->items->count());
- $this->assertEquals(1, $xml->updated[0]->items[0]->item->count());
-
- // Try to add full-text content as user 1
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $content = "This is some full-text content.";
- $totalChars = 2500;
-
- $xmlstr = ''
- . ''
- . ''
- . htmlspecialchars($content)
- . ' '
- . ' '
- . '';
- $response = Sync::upload(self::$sessionID, $updateKey, $xmlstr);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Retrieve it as user 2
- $xml = Sync::updated($user2SessionID, 1, false, false, ["ft" => 1]);
- $this->assertEquals(0, $xml->updated[0]->fulltexts->count());
-
- API::groupClear(self::$config['ownedPrivateGroupID2']);
- }
-}
diff --git a/tests/remote/tests/Sync/ItemTest.php b/tests/remote/tests/Sync/ItemTest.php
deleted file mode 100644
index 6abe8650..00000000
--- a/tests/remote/tests/Sync/ItemTest.php
+++ /dev/null
@@ -1,174 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-use API2 as API;
-require_once 'include/api2.inc.php';
-require_once 'include/sync.inc.php';
-
-class SyncItemTests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $sessionID;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- API::useAPIVersion(2);
- }
-
-
- public function setUp() {
- API::userClear(self::$config['userID']);
- self::$sessionID = Sync::login();
- }
-
-
- public function tearDown() {
- Sync::logout(self::$sessionID);
- self::$sessionID = null;
- }
-
-
- public function testCachedItem() {
- $itemKey = Sync::createItem(
- self::$sessionID, self::$config['libraryID'], "book", array(
- "title" => "Test",
- "numPages" => "204"
- ), $this
- );
-
- Sync::updated(self::$sessionID);
-
- $xml = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $json['creators'] = array(
- array(
- "firstName" => "First",
- "lastName" => "Last",
- "creatorType" => "author"
- )
- );
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assertEquals(204, $response->getStatus());
-
- $xml = Sync::updated(self::$sessionID);
- $this->assertEquals("Test", $xml->updated[0]->items[0]->item[0]->field[0]);
- $this->assertEquals("204", $xml->updated[0]->items[0]->item[0]->field[1]);
- $this->assertEquals(1, $xml->updated[0]->items[0]->item[0]->creator->count());
-
- // Fully cached response
- $xml = Sync::updated(self::$sessionID);
- $this->assertEquals("Test", $xml->updated[0]->items[0]->item[0]->field[0]);
- $this->assertEquals("204", $xml->updated[0]->items[0]->item[0]->field[1]);
- $this->assertEquals(1, $xml->updated[0]->items[0]->item[0]->creator->count());
-
- // Item-level caching
- $xml = Sync::updated(self::$sessionID, 2);
- $this->assertEquals("Test", $xml->updated[0]->items[0]->item[0]->field[0]);
- $this->assertEquals("204", $xml->updated[0]->items[0]->item[0]->field[1]);
- $this->assertEquals(1, $xml->updated[0]->items[0]->item[0]->creator->count());
-
- $xml = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
-
- $json['title'] = "Test 2";
- $json['creators'] = array(
- array(
- "firstName" => "First",
- "lastName" => "Last",
- "creatorType" => "author"
- ),
- array(
- "name" => "Test Name",
- "creatorType" => "editor"
- )
- );
-
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
-
- $xml = Sync::updated(self::$sessionID);
- $this->assertEquals("Test 2", $xml->updated[0]->items[0]->item[0]->field[0]);
- $this->assertEquals("204", $xml->updated[0]->items[0]->item[0]->field[1]);
- $this->assertEquals(2, $xml->updated[0]->items[0]->item[0]->creator->count());
-
- $xml = Sync::updated(self::$sessionID, 3);
- $this->assertEquals("Test 2", $xml->updated[0]->items[0]->item[0]->field[0]);
- $this->assertEquals("204", $xml->updated[0]->items[0]->item[0]->field[1]);
- $this->assertEquals(2, $xml->updated[0]->items[0]->item[0]->creator->count());
- }
-
-
- public function testComputerProgram() {
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
- $itemKey = 'AAAAAAAA';
-
- // Create item via sync
- $data = '- '
- . '
1.0 '
- . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data);
- Sync::waitForUpload(self::$sessionID, $response, $this);
-
- // Get item version via API
- $response = API::userGet(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'] . "&content=json"
- );
- $this->assertEquals(200, $response->getStatus());
- $xml = API::getItemXML($itemKey);
- $data = API::parseDataFromAtomEntry($xml);
- $json = json_decode($data['content'], true);
- $this->assertEquals('1.0', $json['version']);
-
- $json['version'] = '1.1';
- $response = API::userPut(
- self::$config['userID'],
- "items/$itemKey?key=" . self::$config['apiKey'],
- json_encode($json)
- );
- $this->assertEquals(204, $response->getStatus());
-
- $xml = Sync::updated(self::$sessionID);
- $this->assertEquals('version', (string) $xml->updated[0]->items[0]->item[0]->field[0]['name']);
- }
-}
diff --git a/tests/remote/tests/Sync/NoteTest.php b/tests/remote/tests/Sync/NoteTest.php
deleted file mode 100644
index a7a61d42..00000000
--- a/tests/remote/tests/Sync/NoteTest.php
+++ /dev/null
@@ -1,221 +0,0 @@
-.
-
- ***** END LICENSE BLOCK *****
-*/
-
-use API2 as API;
-require_once 'include/api2.inc.php';
-require_once 'include/sync.inc.php';
-
-class SyncNoteTests extends PHPUnit_Framework_TestCase {
- protected static $config;
- protected static $sessionID;
-
- public static function setUpBeforeClass() {
- require 'include/config.inc.php';
- foreach ($config as $k => $v) {
- self::$config[$k] = $v;
- }
-
- API::useAPIVersion(2);
- }
-
-
- public function setUp() {
- API::userClear(self::$config['userID']);
- API::groupClear(self::$config['ownedPrivateGroupID']);
- API::groupClear(self::$config['ownedPublicGroupID']);
- self::$sessionID = Sync::login();
- Sync::useZoteroVersion();
- }
-
-
- public function tearDown() {
- Sync::useZoteroVersion();
- Sync::logout(self::$sessionID);
- self::$sessionID = null;
- }
-
-
- public function testNoteTooLong() {
- $xml = Sync::updated(self::$sessionID);
- $updateKey = (string) $xml['updateKey'];
-
- $content = str_repeat("1234567890", 25001);
-
- // Create too-long note via sync
- $data = '' . $content . ' ';
-
- // Create too-long note with content within HTML tags
- $content = "";
-
- //
- // < 4.0.27
- //
- Sync::useZoteroVersion("4.0.26.4");
- $response = Sync::upload(self::$sessionID, $updateKey, $data, true);
- $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true);
-
- $this->assertTrue(isset($xml->error));
- $this->assertEquals("ERROR_PROCESSING_UPLOAD_DATA", $xml->error["code"]);
- $this->assertRegExp('/^The note \'.+\' in your library is too long /', (string) $xml->error);
- $this->assertRegExp('/ copy and paste \'AAAAAAAA\' into /', (string) $xml->error);
-
- $data = '' . htmlentities($content) . ' ';
- $response = Sync::upload(self::$sessionID, $updateKey, $data, true);
- $xml = Sync::waitForUpload(self::$sessionID, $response, $this, true);
-
- $this->assertTrue(isset($xml->error));
- $this->assertEquals("ERROR_PROCESSING_UPLOAD_DATA", $xml->error["code"]);
- $this->assertRegExp('/^The note \'