public function collections() { if (($this->method == 'POST' || $this->method == 'PUT') && !$this->body) { $this->e400("{$this->method} data not provided"); } $collections = array(); // Single collection if (($this->objectID || $this->objectKey) && $this->subset != 'collections') { $this->allowMethods(array('GET', 'PUT', 'DELETE')); // If id, redirect to key URL if ($this->objectID) { $this->allowMethods(array('GET')); $collection = Zotero_Collections::get($this->objectLibraryID, $this->objectID); if (!$collection) { $this->e404("Collection not found"); } $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''; header("Location: " . Zotero_API::getCollectionURI($collection) . $qs); exit; } $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->objectKey); if (!$collection) { $this->e404("Collection not found"); } // In single-collection mode, require public pref to be enabled if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } if ($this->method == 'PUT' || $this->method == 'DELETE') { if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } if (!Z_CONFIG::$TESTING_SITE || empty($_GET['skipetag'])) { if (empty($_SERVER['HTTP_IF_MATCH'])) { $this->e400("If-Match header not provided"); } if (!preg_match('/^"?([a-f0-9]{32})"?$/', $_SERVER['HTTP_IF_MATCH'], $matches)) { $this->e400("Invalid ETag in If-Match header"); } if ($collection->etag != $matches[1]) { $this->e412("ETag does not match current version of collection"); } } if ($this->method == 'PUT') { $obj = $this->jsonDecode($this->body); Zotero_Collections::updateFromJSON($collection, $obj); $this->queryParams['format'] = 'atom'; $this->queryParams['content'] = array('json'); if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } } else { Zotero_Collections::delete($this->objectLibraryID, $this->objectKey, true); $this->e204(); } } $this->responseXML = Zotero_Collections::convertCollectionToAtom($collection, $this->queryParams['content']); } else { $this->allowMethods(array('GET', 'POST')); if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } if ($this->scopeObject) { $this->allowMethods(array('GET')); switch ($this->scopeObject) { case 'collections': // If id, redirect to key URL if ($this->scopeObjectID) { $collection = Zotero_Collections::get($this->objectLibraryID, $this->scopeObjectID); if (!$collection) { $this->e404("Collection not found"); } $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''; header("Location: " . Zotero_API::getCollectionURI($collection) . $qs); exit; } $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey); if (!$collection) { $this->e404("Collection not found"); } $title = "Child Collections of ‘{$collection->name}'’"; $collectionIDs = $collection->getChildCollections(); break; default: throw new Exception("Invalid collections scope object '{$this->scopeObject}'"); } } else { // Top-level items if ($this->subset == 'top') { $this->allowMethods(array('GET')); $title = "Top-Level Collections"; $results = Zotero_Collections::getAllAdvanced($this->objectLibraryID, true, $this->queryParams); } else { // Create a collection if ($this->method == 'POST') { if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } $obj = $this->jsonDecode($this->body); $collection = Zotero_Collections::addFromJSON($obj, $this->objectLibraryID); if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } $uri = Zotero_API::getCollectionURI($collection); $queryString = "content=json"; if ($this->apiKey) { $queryString .= "&key=" . $this->apiKey; } $uri .= "?" . $queryString; $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, true); } $title = "Collections"; $results = Zotero_Collections::getAllAdvanced($this->objectLibraryID, false, $this->queryParams); } $collections = $results['collections']; $totalResults = $results['total']; } if (!empty($collectionIDs)) { foreach ($collectionIDs as $collectionID) { $collections[] = Zotero_Collections::get($this->objectLibraryID, $collectionID); } // Fake sorting and limiting $totalResults = sizeOf($collections); $key = $this->queryParams['order']; if ($key == 'title') { $key = 'name'; } $dir = $this->queryParams['sort']; usort($collections, function ($a, $b) use($key, $dir) { $dir = $dir == "asc" ? 1 : -1; if ($a->{$key} == $b->{$key}) { return 0; } else { return $a->{$key} > $b->{$key} ? $dir : $dir * -1; } }); $collections = array_slice($collections, $this->queryParams['start'], $this->queryParams['limit']); } $this->responseXML = Zotero_Atom::createAtomFeed($this->getFeedNamePrefix($this->objectLibraryID) . $title, $this->uri, $collections, $totalResults, $this->queryParams, $this->apiVersion, $this->permissions); } $this->end(); }
public static function createAtomFeed($action, $title, $url, $entries, $totalResults = null, $queryParams = [], Zotero_Permissions $permissions = null, $fixedValues = array()) { if ($queryParams) { $nonDefaultParams = Zotero_API::getNonDefaultParams($action, $queryParams); } else { $nonDefaultParams = []; } $feed = '<?xml version="1.0" encoding="UTF-8"?>' . '<feed xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"/>'; $xml = new SimpleXMLElement($feed); $xml->title = $title; $path = parse_url($url, PHP_URL_PATH); // Generate canonical URI $zoteroURI = Zotero_URI::getBaseURI() . substr($path, 1) . Zotero_API::buildQueryString($queryParams['v'], $action, $nonDefaultParams, ['v']); $baseURI = Zotero_API::getBaseURI() . substr($path, 1); // API version isn't included in URLs (as with the API key) // // It could alternatively be made a private parameter so that it didn't appear // in the Link header either, but for now it's still there. $excludeParams = ['v']; $links = Zotero_API::buildLinks($action, $path, $totalResults, $queryParams, $nonDefaultParams, $excludeParams); $xml->id = $zoteroURI; $link = $xml->addChild("link"); $link['rel'] = "self"; $link['type'] = "application/atom+xml"; $link['href'] = $links['self']; $link = $xml->addChild("link"); $link['rel'] = "first"; $link['type'] = "application/atom+xml"; $link['href'] = $links['first']; if (isset($links['next'])) { $link = $xml->addChild("link"); $link['rel'] = "next"; $link['type'] = "application/atom+xml"; $link['href'] = $links['next']; } $link = $xml->addChild("link"); $link['rel'] = "last"; $link['type'] = "application/atom+xml"; $link['href'] = $links['last']; // Generate alternate URI $link = $xml->addChild("link"); $link['rel'] = "alternate"; $link['type'] = "text/html"; $link['href'] = $links['alternate']; if ($queryParams['v'] < 3) { $xml->addChild("zapi:totalResults", is_numeric($totalResults) ? $totalResults : sizeOf($entries), self::$nsZoteroAPI); } if ($queryParams['v'] < 2) { $xml->addChild("zapi:apiVersion", 1, self::$nsZoteroAPI); } $latestUpdated = ''; // Check memcached for bib data $sharedData = array(); if ($entries && $entries[0] instanceof Zotero_Item) { if (in_array('citation', $queryParams['content'])) { $sharedData["citation"] = Zotero_Cite::multiGetFromMemcached("citation", $entries, $queryParams); } if (in_array('bib', $queryParams['content'])) { $sharedData["bib"] = Zotero_Cite::multiGetFromMemcached("bib", $entries, $queryParams); } } $xmlEntries = array(); foreach ($entries as $entry) { if ($entry->dateModified > $latestUpdated) { $latestUpdated = $entry->dateModified; } if ($entry instanceof SimpleXMLElement) { $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Collection) { $entry = Zotero_Collections::convertCollectionToAtom($entry, $queryParams); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Item) { $entry = Zotero_Items::convertItemToAtom($entry, $queryParams, $permissions, $sharedData); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Search) { $entry = $entry->toAtom($queryParams); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Tag) { $xmlEntries[] = $entry->toAtom($queryParams, isset($fixedValues[$entry->id]) ? $fixedValues[$entry->id] : null); } else { if ($entry instanceof Zotero_Group) { $entry = $entry->toAtom($queryParams); $xmlEntries[] = $entry; } } } } } } } if ($latestUpdated) { $xml->updated = Zotero_Date::sqlToISO8601($latestUpdated); } else { $xml->updated = str_replace("+00:00", "Z", date('c')); } // Import object XML nodes into document $doc = dom_import_simplexml($xml); foreach ($xmlEntries as $xmlEntry) { $subNode = dom_import_simplexml($xmlEntry); $importedNode = $doc->ownerDocument->importNode($subNode, true); $doc->appendChild($importedNode); } return $xml; }
public function collections() { // Check for general library access if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } if ($this->isWriteMethod()) { // Check for library write access if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } // Make sure library hasn't been modified if (!$this->singleObject) { $libraryTimestampChecked = $this->checkLibraryIfUnmodifiedSinceVersion(); } Zotero_Libraries::updateVersionAndTimestamp($this->objectLibraryID); } $collectionIDs = array(); $collectionKeys = array(); $results = array(); // Single collection if ($this->singleObject) { $this->allowMethods(['HEAD', 'GET', 'PUT', 'PATCH', 'DELETE']); if (!Zotero_ID::isValidKey($this->objectKey)) { $this->e404(); } $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->objectKey); if ($this->isWriteMethod()) { $collection = $this->handleObjectWrite('collection', $collection ? $collection : null); $this->queryParams['content'] = ['json']; } if (!$collection) { $this->e404("Collection not found"); } $this->libraryVersion = $collection->version; if ($this->method == 'HEAD') { $this->end(); } switch ($this->queryParams['format']) { case 'atom': $this->responseXML = Zotero_Collections::convertCollectionToAtom($collection, $this->queryParams); break; case 'json': $json = $collection->toResponseJSON($this->queryParams, $this->permissions); echo Zotero_Utilities::formatJSON($json); break; default: throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'"); } } else { $this->allowMethods(['HEAD', 'GET', 'POST', 'DELETE']); $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID); if ($this->scopeObject) { $this->allowMethods(array('GET')); switch ($this->scopeObject) { case 'collections': $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey); if (!$collection) { $this->e404("Collection not found"); } $title = "Child Collections of ‘{$collection->name}'’"; $collectionIDs = $collection->getChildCollections(); break; default: throw new Exception("Invalid collections scope object '{$this->scopeObject}'"); } } else { // Top-level items if ($this->subset == 'top') { $this->allowMethods(array('GET')); $title = "Top-Level Collections"; $results = Zotero_Collections::search($this->objectLibraryID, true, $this->queryParams); } else { // Create a collection if ($this->method == 'POST') { $this->queryParams['format'] = 'writereport'; $obj = $this->jsonDecode($this->body); $results = Zotero_Collections::updateMultipleFromJSON($obj, $this->queryParams, $this->objectLibraryID, $this->userID, $this->permissions, $libraryTimestampChecked ? 0 : 1, null); if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } if ($this->apiVersion < 2) { $uri = Zotero_API::getCollectionsURI($this->objectLibraryID); $keys = array_merge(get_object_vars($results['success']), get_object_vars($results['unchanged'])); $queryString = "collectionKey=" . urlencode(implode(",", $keys)) . "&format=atom&content=json&order=collectionKeyList&sort=asc"; if ($this->apiKey) { $queryString .= "&key=" . $this->apiKey; } $uri .= "?" . $queryString; $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, true, $this->apiVersion); $title = "Collections"; $results = Zotero_Collections::search($this->objectLibraryID, false, $this->queryParams); } } else { if ($this->method == 'DELETE') { Zotero_DB::beginTransaction(); foreach ($this->queryParams['collectionKey'] as $collectionKey) { Zotero_Collections::delete($this->objectLibraryID, $collectionKey); } Zotero_DB::commit(); $this->e204(); } else { $title = "Collections"; $results = Zotero_Collections::search($this->objectLibraryID, false, $this->queryParams); } } } } if ($collectionIDs) { $this->queryParams['collectionIDs'] = $collectionIDs; $results = Zotero_Collections::search($this->objectLibraryID, false, $this->queryParams); } $options = ['action' => $this->action, 'uri' => $this->uri, 'results' => $results, 'requestParams' => $this->queryParams, 'permissions' => $this->permissions, 'head' => $this->method == 'HEAD']; switch ($this->queryParams['format']) { case 'atom': $this->responseXML = Zotero_API::multiResponse(array_merge($options, ['title' => $this->getFeedNamePrefix($this->objectLibraryID) . $title])); break; case 'json': case 'keys': case 'versions': case 'writereport': Zotero_API::multiResponse($options); break; default: throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'"); } } $this->end(); }
public static function createAtomFeed($title, $url, $entries, $totalResults = null, $queryParams = null, $apiVersion = null, $permissions = null, $fixedValues = array()) { if ($queryParams) { $nonDefaultParams = Zotero_API::getNonDefaultQueryParams($queryParams); // Convert 'content' array to sorted comma-separated string if (isset($nonDefaultParams['content'])) { $nonDefaultParams['content'] = implode(',', $nonDefaultParams['content']); } } else { $nonDefaultParams = array(); } $feed = '<feed xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"'; if ($queryParams && $queryParams['content'][0] == 'full') { $feed .= ' xmlns:zxfer="' . Zotero_Atom::$nsZoteroTransfer . '"'; } $feed .= '/>'; $xml = new SimpleXMLElement($feed); $xml->title = $title; $path = parse_url($url, PHP_URL_PATH); // Generate canonical URI $zoteroURI = Zotero_URI::getBaseURI() . substr($path, 1); if ($nonDefaultParams) { $zoteroURI .= "?" . http_build_query($nonDefaultParams); } $atomURI = Zotero_Atom::getBaseURI() . substr($path, 1); // // Generate URIs for 'self', 'first', 'next' and 'last' links // // 'self' $atomSelfURI = $atomURI; if ($nonDefaultParams) { $atomSelfURI .= "?" . http_build_query($nonDefaultParams); } // 'first' $atomFirstURI = $atomURI; if ($nonDefaultParams) { $p = $nonDefaultParams; unset($p['start']); if ($first = http_build_query($p)) { $atomFirstURI .= "?" . $first; } } // 'last' if (!$queryParams['start'] && $queryParams['limit'] >= $totalResults) { $atomLastURI = $atomSelfURI; } else { // 'start' past results if ($queryParams['start'] >= $totalResults) { $lastStart = $totalResults - $queryParams['limit']; } else { $lastStart = $totalResults - $totalResults % $queryParams['limit']; if ($lastStart == $totalResults) { $lastStart = $totalResults - $queryParams['limit']; } } $p = $nonDefaultParams; if ($lastStart > 0) { $p['start'] = $lastStart; } else { unset($p['start']); } $atomLastURI = $atomURI; if ($last = http_build_query($p)) { $atomLastURI .= "?" . $last; } // 'next' $nextStart = $queryParams['start'] + $queryParams['limit']; if ($nextStart < $totalResults) { $p = $nonDefaultParams; $p['start'] = $nextStart; $atomNextURI = $atomURI . "?" . http_build_query($p); } } $xml->id = $zoteroURI; $link = $xml->addChild("link"); $link['rel'] = "self"; $link['type'] = "application/atom+xml"; $link['href'] = $atomSelfURI; $link = $xml->addChild("link"); $link['rel'] = "first"; $link['type'] = "application/atom+xml"; $link['href'] = $atomFirstURI; if (isset($atomNextURI)) { $link = $xml->addChild("link"); $link['rel'] = "next"; $link['type'] = "application/atom+xml"; $link['href'] = $atomNextURI; } $link = $xml->addChild("link"); $link['rel'] = "last"; $link['type'] = "application/atom+xml"; $link['href'] = $atomLastURI; // Generate alternate URI $alternateURI = Zotero_URI::getBaseURI() . substr($path, 1); if ($nonDefaultParams) { $p = $nonDefaultParams; if (isset($p['content'])) { unset($p['content']); } if ($p) { $alternateURI .= "?" . http_build_query($p); } } $link = $xml->addChild("link"); $link['rel'] = "alternate"; $link['type'] = "text/html"; $link['href'] = $alternateURI; $xml->addChild("zapi:totalResults", is_numeric($totalResults) ? $totalResults : sizeOf($entries), self::$nsZoteroAPI); $xml->addChild("zapi:apiVersion", $apiVersion, self::$nsZoteroAPI); $latestUpdated = ''; // Get bib data using parallel requests $sharedData = array(); if ($entries && $entries[0] instanceof Zotero_Item) { if (in_array('citation', $queryParams['content'])) { $sharedData["citation"] = Zotero_Cite::multiGetFromCiteServer("citation", $entries, $queryParams['style']); } if (in_array('bib', $queryParams['content'])) { $sharedData["bib"] = Zotero_Cite::multiGetFromCiteServer("bib", $entries, $queryParams['style']); } } $xmlEntries = array(); foreach ($entries as $entry) { if ($entry->dateModified > $latestUpdated) { $latestUpdated = $entry->dateModified; } if ($entry instanceof SimpleXMLElement) { $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Collection) { $entry = Zotero_Collections::convertCollectionToAtom($entry, $queryParams['content'], $apiVersion); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Creator) { $entry = Zotero_Creators::convertCreatorToAtom($entry, $queryParams['content'], $apiVersion); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Item) { $entry = Zotero_Items::convertItemToAtom($entry, $queryParams, $apiVersion, $permissions, $sharedData); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Search) { $entry = Zotero_Searches::convertSearchToAtom($entry, $queryParams['content'], $apiVersion); $xmlEntries[] = $entry; } else { if ($entry instanceof Zotero_Tag) { $xmlEntries[] = $entry->toAtom($queryParams['content'], $apiVersion, isset($fixedValues[$entry->id]) ? $fixedValues[$entry->id] : null); } else { if ($entry instanceof Zotero_Group) { $entry = $entry->toAtom($queryParams['content'], $apiVersion); $xmlEntries[] = $entry; } } } } } } } } if ($latestUpdated) { $xml->updated = Zotero_Date::sqlToISO8601($latestUpdated); } else { $xml->updated = str_replace("+00:00", "Z", date('c')); } // Import object XML nodes into document $doc = dom_import_simplexml($xml); foreach ($xmlEntries as $xmlEntry) { $subNode = dom_import_simplexml($xmlEntry); $importedNode = $doc->ownerDocument->importNode($subNode, true); $doc->appendChild($importedNode); } return $xml; }