public static function getUserURI($userID, $skipNames = false) { if ($skipNames) { return self::getBaseURI() . "users/{$userID}"; } $username = Zotero_Users::getUsername($userID); return self::getBaseURI() . Zotero_Utilities::slugify($username); }
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 getName($libraryID) { $type = self::getType($libraryID); switch ($type) { case 'user': $userID = Zotero_Users::getUserIDFromLibraryID($libraryID); return Zotero_Users::getUsername($userID); case 'group': $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID); $group = Zotero_Groups::get($groupID); return $group->name; } }
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}'"); } }
/** * 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(); $downloadDetails = $permissions->canAccess($item->libraryID, 'files') ? Zotero_Storage::getDownloadDetails($item) : false; if ($isRegularItem) { $numChildren = $permissions->canAccess($item->libraryID, 'notes') ? $item->numChildren() : $item->numAttachments(); } // <id> 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'); $cachedParams = Z_Array::filterKeys($queryParams, $allowedParams); $cacheVersion = 2; $cacheKey = "atomEntry_" . $item->libraryID . "/" . $item->id . "_" . md5($version . json_encode($cachedParams) . ($downloadDetails ? 'hasFile' : '') . ($libraryType == 'group' ? 'id' . $id : '')) . "_" . $queryParams['v'] . "_" . $cacheVersion . (isset(Z_CONFIG::$CACHE_VERSION_ATOM_ENTRY) ? "_" . Z_CONFIG::$CACHE_VERSION_ATOM_ENTRY : "") . (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 <content> // subelements. Otherwise the subelements become, say, // <default:span xmlns="http://www.w3.org/1999/xhtml"> instead // of just <span xmlns="http://www.w3.org/1999/xhtml">, and // xmlns:default="http://www.w3.org/1999/xhtml" gets added to // the parent <entry>. 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); } 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 version="1.0" encoding="UTF-8"?>' . '<entry xmlns="' . Zotero_Atom::$nsAtom . '" xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"/>'; $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) { $author->name = Zotero_Users::getUsername($createdByUserID); $author->uri = Zotero_URI::getUserURI($createdByUserID); } 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); 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); 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) { $xml->addChild('zapi:lastModifiedByUser', Zotero_Users::getUsername($lastModifiedByUserID), Zotero_Atom::$nsZoteroAPI); } $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 !== '') { 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 <content> // or a <multicontent> <content> 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); $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)) { $export = Zotero_Translate::doExport(array($item), $type); $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('<zapi:year></zapi:year>', '<zapi:year/>', $uncached); $uncached = str_replace('<content zapi:type="none"></content>', '<content zapi:type="none"/>', $uncached); $uncached = str_replace('<zapi:subcontent zapi:type="coins" type="text/html"></zapi:subcontent>', '<zapi:subcontent zapi:type="coins" type="text/html"/>', $uncached); $uncached = str_replace('<title></title>', '<title/>', $uncached); $uncached = str_replace('<note></note>', '<note/>', $uncached); $uncached = str_replace('<path></path>', '<path/>', $uncached); $uncached = str_replace('<td></td>', '<td/>', $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; }
public function groups() { if (($this->method == 'POST' || $this->method == 'PUT') && !$this->body) { $this->e400("{$this->method} data not provided"); } $groupID = $this->groupID; // // Add a group // if ($this->method == 'POST') { if (!$this->permissions->isSuper()) { $this->e403(); } if ($groupID) { $this->e400("POST requests cannot end with a groupID (did you mean PUT?)"); } try { $group = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } if ((int) $group['id']) { $this->e400("POST requests cannot contain a groupID in '" . $this->body . "'"); } $fields = $this->getFieldsFromGroupXML($group); Zotero_DB::beginTransaction(); try { $group = new Zotero_Group(); foreach ($fields as $field => $val) { $group->{$field} = $val; } $group->save(); } catch (Exception $e) { if (strpos($e->getMessage(), "Invalid") === 0) { $this->e400($e->getMessage() . " in " . $this->body . "'"); } switch ($e->getCode()) { case Z_ERROR_GROUP_NAME_UNAVAILABLE: $this->e400($e->getMessage()); default: $this->e500($e->getMessage()); } } $this->responseXML = $group->toAtom(array('full'), $this->queryParams, $this->apiVersion); Zotero_DB::commit(); $url = Zotero_Atom::getGroupURI($group); header("Location: " . $url, false, 201); $this->end(); } // // Update a group // if ($this->method == 'PUT') { if (!$this->permissions->isSuper()) { $this->e403(); } if (!$groupID) { $this->e400("PUT requests must end with a groupID (did you mean POST?)"); } try { $group = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } $fields = $this->getFieldsFromGroupXML($group); // Group id is optional, but, if it's there, make sure it matches $id = (string) $group['id']; if ($id && $id != $groupID) { $this->e400("Group ID {$id} does not match group ID {$groupID} from URI"); } Zotero_DB::beginTransaction(); try { $group = Zotero_Groups::get($groupID); if (!$group) { $this->e404("Group {$groupID} does not exist"); } foreach ($fields as $field => $val) { $group->{$field} = $val; } if ($this->ifUnmodifiedSince && strtotime($group->dateModified) > $this->ifUnmodifiedSince) { $this->e412(); } $group->save(); } catch (Exception $e) { if (strpos($e->getMessage(), "Invalid") === 0) { $this->e400($e->getMessage() . " in " . $this->body . "'"); } else { if ($e->getCode() == Z_ERROR_GROUP_DESCRIPTION_TOO_LONG) { $this->e400($e->getMessage()); } } $this->e500($e->getMessage()); } $this->responseXML = $group->toAtom(array('full'), $this->queryParams, $this->apiVersion); Zotero_DB::commit(); $this->end(); } // // Delete a group // if ($this->method == 'DELETE') { if (!$this->permissions->isSuper()) { $this->e403(); } if (!$groupID) { $this->e400("DELETE requests must end with a groupID"); } Zotero_DB::beginTransaction(); $group = Zotero_Groups::get($groupID); if (!$group) { $this->e404("Group {$groupID} does not exist"); } $group->erase(); Zotero_DB::commit(); header("HTTP/1.1 204 No Content"); exit; } // // View one or more groups // // Single group if ($groupID) { $group = Zotero_Groups::get($groupID); if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } if (!$group) { $this->e404("Group not found"); } $this->responseXML = $group->toAtom($this->queryParams['content'], $this->queryParams, $this->apiVersion); } else { if ($this->objectUserID) { // Users (or their keys) can see only their own groups if (!$this->permissions->isSuper() && $this->userID != $this->objectUserID) { $this->e403(); } $title = Zotero_Users::getUsername($this->objectUserID) . "’s Groups"; } else { // For now, only root can do unrestricted group searches if (!$this->permissions->isSuper()) { $this->e403(); } $title = "Groups"; } try { $results = Zotero_Groups::getAllAdvanced($this->objectUserID, $this->queryParams, $this->permissions); } catch (Exception $e) { switch ($e->getCode()) { case Z_ERROR_INVALID_GROUP_TYPE: $this->e400($e->getMessage()); } throw $e; } $groups = $results['groups']; $totalResults = $results['totalResults']; $this->responseXML = Zotero_Atom::createAtomFeed($title, $this->uri, $groups, $totalResults, $this->queryParams, $this->apiVersion, $this->permissions); } $this->end(); }
public function memberToAtom($userID) { if (!is_int($userID)) { throw new Exception("userID must be an integer (was " . gettype($userID) . ")"); } if (!$this->loaded) { $this->load(); } $groupUserData = $this->getUserData($userID); if (!$groupUserData) { throw new Exception("User {$userID} is not a member of group {$this->id}", Z_ERROR_USER_NOT_GROUP_MEMBER); } $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?>' . '<entry xmlns="' . Zotero_Atom::$nsAtom . '" ' . 'xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '" ' . 'xmlns:xfer="' . Zotero_Atom::$nsZoteroTransfer . '"/>'); // If we know the username, provide that // TODO: get and cache full names if (Zotero_Users::exists($userID)) { $title = Zotero_Users::getUsername($userID); } else { $title = "User {$userID}"; } $xml->title = $title; $author = $xml->addChild('author'); $author->name = "Zotero"; $author->uri = "http://zotero.org"; $xml->id = Zotero_URI::getGroupUserURI($this, $userID); $xml->published = Zotero_Date::sqlToISO8601($groupUserData['joined']); $xml->updated = Zotero_Date::sqlToISO8601($groupUserData['lastUpdated']); $link = $xml->addChild("link"); $link['rel'] = "self"; $link['type'] = "application/atom+xml"; $link['href'] = Zotero_API::getGroupUserURI($this, $userID); $link = $xml->addChild('link'); $link['rel'] = 'alternate'; $link['type'] = 'text/html'; $link['href'] = Zotero_URI::getGroupUserURI($this, $userID); $xml->content['type'] = 'application/xml'; $userXML = new SimpleXMLElement('<user xmlns="' . Zotero_Atom::$nsZoteroTransfer . '"/>'); // This method of adding the element seems to be necessary to get the // namespace prefix to show up $fNode = dom_import_simplexml($xml->content); $subNode = dom_import_simplexml($userXML); $importedNode = $fNode->ownerDocument->importNode($subNode, true); $fNode->appendChild($importedNode); $xml->content->user['id'] = $userID; $xml->content->user['role'] = $groupUserData['role']; return $xml; }
/** * Converts a Zotero_Item object to a SimpleXMLElement Atom object * * @param object $item Zotero_Item object * @param string $content * @return SimpleXMLElement Item data as SimpleXML element */ public static function convertItemToAtom(Zotero_Item $item, $queryParams, $apiVersion = null, $permissions = null, $sharedData = null) { $content = $queryParams['content']; $contentIsHTML = sizeOf($content) == 1 && $content[0] == 'html'; $contentParamString = urlencode(implode(',', $content)); $style = $queryParams['style']; $entry = '<entry xmlns="' . Zotero_Atom::$nsAtom . '" xmlns:zapi="' . Zotero_Atom::$nsZoteroAPI . '"/>'; $xml = new SimpleXMLElement($entry); $title = $item->getDisplayTitle(true); $title = $title ? $title : '[Untitled]'; $xml->title = $title; $author = $xml->addChild('author'); $createdByUserID = null; switch (Zotero_Libraries::getType($item->libraryID)) { case 'group': $createdByUserID = $item->createdByUserID; break; } if ($createdByUserID) { $author->name = Zotero_Users::getUsername($createdByUserID); $author->uri = Zotero_URI::getUserURI($createdByUserID); } else { $author->name = Zotero_Libraries::getName($item->libraryID); $author->uri = Zotero_URI::getLibraryURI($item->libraryID); } $id = Zotero_URI::getItemURI($item); /*if (!$contentIsHTML) { $id .= "?content=$content"; }*/ $xml->id = $id; $xml->published = Zotero_Date::sqlToISO8601($item->getField('dateAdded')); $xml->updated = Zotero_Date::sqlToISO8601($item->getField('dateModified')); $link = $xml->addChild("link"); $link['rel'] = "self"; $link['type'] = "application/atom+xml"; $href = Zotero_Atom::getItemURI($item); if (!$contentIsHTML) { $href .= "?content={$contentParamString}"; } $link['href'] = $href; $parent = $item->getSource(); 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_Atom::getItemURI($parentItem); 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); // If appropriate permissions and the file is stored in ZFS, get file request link if ($permissions && $permissions->canAccess($item->libraryID, 'files')) { $details = Zotero_S3::getDownloadDetails($item); if ($details) { $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 (!empty($details['size'])) { $link['length'] = $details['size']; } } } $xml->addChild('zapi:key', $item->key, Zotero_Atom::$nsZoteroAPI); $xml->addChild('zapi:itemType', Zotero_ItemTypes::getName($item->itemTypeID), Zotero_Atom::$nsZoteroAPI); if ($item->isRegularItem()) { $val = $item->creatorSummary; if ($val !== '') { $xml->addChild('zapi:creatorSummary', htmlspecialchars($val), Zotero_Atom::$nsZoteroAPI); } $val = substr($item->getField('date', true, true, true), 0, 4); if ($val !== '' && $val !== '0000') { $xml->addChild('zapi:year', $val, Zotero_Atom::$nsZoteroAPI); } } if (!$parent && $item->isRegularItem()) { if ($permissions && !$permissions->canAccess($item->libraryID, 'notes')) { $numChildren = $item->numAttachments(); } else { $numChildren = $item->numChildren(); } $xml->addChild('zapi:numChildren', $numChildren, Zotero_Atom::$nsZoteroAPI); } $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 <content> // or a <multicontent> <content> 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->createElement('div'); $div->setAttribute('xmlns', Zotero_Atom::$nsXHTML); $target->appendChild($div); $html = $item->toHTML(true); $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, $style); } $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), $style); } $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') { $target->setAttributeNS(Zotero_Atom::$nsZoteroAPI, "zapi:etag", $item->etag); $textNode = $domDoc->createTextNode($item->toJSON(false, $queryParams['pprint'], true)); $target->appendChild($textNode); } else { if ($type == 'csljson') { $arr = $item->toCSLItem(); $mask = JSON_HEX_TAG | JSON_HEX_AMP; if ($queryParams['pprint']) { $json = Zotero_Utilities::json_encode_pretty($arr, $mask); } else { $json = json_encode($arr, $mask); } // Until JSON_UNESCAPED_SLASHES is available $json = str_replace('\\/', '/', $json); $textNode = $domDoc->createTextNode($json); $target->appendChild($textNode); } else { if ($type == 'full') { if (!$multiFormat) { $target->setAttribute('type', 'xhtml'); } $fullXML = Zotero_Items::convertItemToXML($item, array(), $apiVersion); $fullXML->addAttribute("xmlns", Zotero_Atom::$nsZoteroTransfer); $subNode = dom_import_simplexml($fullXML); $importedNode = $domDoc->importNode($subNode, true); $target->appendChild($importedNode); } else { if (in_array($type, Zotero_Translate::$exportFormats)) { $export = Zotero_Translate::doExport(array($item), $type); $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); } } } } } } } } } return $xml; }
public static function deleteUser($userID) { if (empty($userID)) { throw new Exception("userID not provided"); } $username = Zotero_Users::getUsername($userID, true); $sql = "SELECT LUM_Role.Name FROM LUM_User JOIN LUM_Role USING (RoleID) WHERE UserID=?"; try { $role = Zotero_WWW_DB_2::valueQuery($sql, $userID); } catch (Exception $e) { Z_Core::logError("WARNING: {$e} -- retrying on primary"); $role = Zotero_WWW_DB_1::valueQuery($sql, $userID); } if ($role != 'Deleted') { throw new Exception("User '{$username}' does not have role 'Deleted'"); } 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(); }
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); } }
public function toJSON() { if (($this->id || $this->key) && !$this->loaded) { $this->load(); } $json = []; $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 groups() { $groupID = $this->objectGroupID; // // Add a group // if ($this->method == 'POST') { if (!$this->permissions->isSuper()) { $this->e403(); } if ($groupID) { $this->e400("POST requests cannot end with a groupID (did you mean PUT?)"); } try { $group = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } if ((int) $group['id']) { $this->e400("POST requests cannot contain a groupID in '" . $this->body . "'"); } $fields = $this->getFieldsFromGroupXML($group); Zotero_DB::beginTransaction(); try { $group = new Zotero_Group(); foreach ($fields as $field => $val) { $group->{$field} = $val; } $group->save(); } catch (Exception $e) { if (strpos($e->getMessage(), "Invalid") === 0) { $this->e400($e->getMessage() . " in " . $this->body . "'"); } switch ($e->getCode()) { case Z_ERROR_GROUP_NAME_UNAVAILABLE: $this->e400($e->getMessage()); default: $this->handleException($e); } } $this->queryParams['content'] = array('full'); $this->responseXML = $group->toAtom($this->queryParams); Zotero_DB::commit(); $url = Zotero_API::getGroupURI($group); $this->responseCode = 201; header("Location: " . $url, false, 201); $this->end(); } // // Update a group // if ($this->method == 'PUT') { if (!$this->permissions->isSuper()) { $this->e403(); } if (!$groupID) { $this->e400("PUT requests must end with a groupID (did you mean POST?)"); } try { $group = @new SimpleXMLElement($this->body); } catch (Exception $e) { $this->e400("{$this->method} data is not valid XML"); } $fields = $this->getFieldsFromGroupXML($group); // Group id is optional, but, if it's there, make sure it matches $id = (string) $group['id']; if ($id && $id != $groupID) { $this->e400("Group ID {$id} does not match group ID {$groupID} from URI"); } Zotero_DB::beginTransaction(); try { $group = Zotero_Groups::get($groupID); if (!$group) { $this->e404("Group {$groupID} does not exist"); } foreach ($fields as $field => $val) { $group->{$field} = $val; } if ($this->ifUnmodifiedSince && strtotime($group->dateModified) > $this->ifUnmodifiedSince) { $this->e412(); } $group->save(); } catch (Exception $e) { if (strpos($e->getMessage(), "Invalid") === 0) { $this->e400($e->getMessage() . " in " . $this->body . "'"); } else { if ($e->getCode() == Z_ERROR_GROUP_DESCRIPTION_TOO_LONG) { $this->e400($e->getMessage()); } } $this->handleException($e); } $this->queryParams['content'] = array('full'); $this->responseXML = $group->toAtom($this->queryParams); Zotero_DB::commit(); $this->end(); } // // Delete a group // if ($this->method == 'DELETE') { if (!$this->permissions->isSuper()) { $this->e403(); } if (!$groupID) { $this->e400("DELETE requests must end with a groupID"); } Zotero_DB::beginTransaction(); $group = Zotero_Groups::get($groupID); if (!$group) { $this->e404("Group {$groupID} does not exist"); } $group->erase(); Zotero_DB::commit(); header("HTTP/1.1 204 No Content"); exit; } // // View one or more groups // // Single group if ($groupID) { $group = Zotero_Groups::get($groupID); if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } if (!$group) { $this->e404("Group not found"); } if ($this->apiVersion >= 3) { $this->libraryVersion = $group->version; } else { header("ETag: " . $group->etag); } if ($this->method == 'HEAD') { $this->end(); } switch ($this->queryParams['format']) { case 'atom': $this->responseXML = $group->toAtom($this->queryParams); break; case 'json': $json = $group->toResponseJSON($this->queryParams); echo Zotero_Utilities::formatJSON($json); break; default: throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'"); } } else { if ($this->objectUserID) { $title = Zotero_Users::getUsername($this->objectUserID) . "’s Groups"; } else { // For now, only root can do unrestricted group searches if (!$this->permissions->isSuper()) { $this->e403(); } $title = "Groups"; } try { $results = Zotero_Groups::getAllAdvanced($this->objectUserID, $this->queryParams, $this->permissions); } catch (Exception $e) { switch ($e->getCode()) { case Z_ERROR_INVALID_GROUP_TYPE: $this->e400($e->getMessage()); } throw $e; } $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' => $title])); break; case 'json': Zotero_API::multiResponse($options); break; case 'etags': case 'versions': $prop = substr($this->queryParams['format'], 0, -1); // remove 's' $newResults = []; foreach ($results['results'] as $group) { $newResults[$group->id] = $group->{$prop}; } $options['results']['results'] = $newResults; Zotero_API::multiResponse($options, 'versions'); break; default: throw new Exception("Unexpected format '" . $this->queryParams['format'] . "'"); } } $this->end(); }