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); }
public function items() { if (($this->method == 'POST' || $this->method == 'PUT') && !$this->body) { $this->e400("{$this->method} data not provided"); } $itemIDs = array(); $responseItems = array(); $responseKeys = array(); $totalResults = null; // // Single item // if (($this->objectID || $this->objectKey) && !$this->subset) { if ($this->fileMode) { if ($this->fileView) { $this->allowMethods(array('GET', 'HEAD', 'POST')); } else { $this->allowMethods(array('GET', 'PUT', 'POST', 'HEAD', 'PATCH')); } } else { $this->allowMethods(array('GET', 'PUT', 'DELETE')); } // Check for general library access if (!$this->permissions->canAccess($this->objectLibraryID)) { //var_dump($this->objectLibraryID); //var_dump($this->permissions); $this->e403(); } if ($this->objectKey) { $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectKey); } else { try { $item = Zotero_Items::get($this->objectLibraryID, $this->objectID); } catch (Exception $e) { if ($e->getCode() == Z_ERROR_OBJECT_LIBRARY_MISMATCH) { $item = false; } else { throw $e; } } } if (!$item) { // Possibly temporary workaround to block unnecessary full syncs if ($this->fileMode && $this->method == 'POST') { // If > 2 requests for missing file, trigger a full sync via 404 $cacheKey = "apiMissingFile_" . $this->objectLibraryID . "_" . ($this->objectKey ? $this->objectKey : $this->objectID); $set = Z_Core::$MC->get($cacheKey); if (!$set) { Z_Core::$MC->set($cacheKey, 1, 86400); } else { if ($set < 2) { Z_Core::$MC->increment($cacheKey); } else { Z_Core::$MC->delete($cacheKey); $this->e404("A file sync error occurred. Please sync again."); } } $this->e500("A file sync error occurred. Please sync again."); } // If we have an id, make sure this isn't really an all-numeric key if ($this->objectID && strlen($this->objectID) == 8 && preg_match('/[0-9]{8}/', $this->objectID)) { $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectID); if ($item) { $this->objectKey = $this->objectID; unset($this->objectID); } } if (!$item) { $this->e404("Item does not exist"); } } if ($item->isNote() && !$this->permissions->canAccess($this->objectLibraryID, 'notes')) { $this->e403(); } // Make sure URL libraryID matches item libraryID if ($this->objectLibraryID != $item->libraryID) { $this->e404("Item does not exist"); } // File access mode if ($this->fileMode) { $this->_handleFileRequest($item); } // If id, redirect to key URL if ($this->objectID) { $this->allowMethods(array('GET')); $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''; header("Location: " . Zotero_API::getItemURI($item) . $qs); exit; } if ($this->scopeObject) { switch ($this->scopeObject) { // Remove item from collection case 'collections': $this->allowMethods(array('DELETE')); if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey); if (!$collection) { $this->e404("Collection not found"); } if (!$collection->hasItem($item->id)) { $this->e404("Item not found in collection"); } Zotero_DB::beginTransaction(); $timestamp = Zotero_Libraries::updateTimestamps($this->objectLibraryID); Zotero_DB::registerTransactionTimestamp($timestamp); $collection->removeItem($item->id); Zotero_DB::commit(); $this->e204(); default: $this->e400(); } } 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 ($item->etag != $matches[1]) { $this->e412("ETag does not match current version of item"); } } // Update existing item if ($this->method == 'PUT') { $obj = $this->jsonDecode($this->body); Zotero_Items::updateFromJSON($item, $obj, false, null, $this->userID); $this->queryParams['format'] = 'atom'; $this->queryParams['content'] = array('json'); if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } } else { Zotero_Items::delete($this->objectLibraryID, $this->objectKey, true); try { Zotero_Processors::notifyProcessors('index'); } catch (Exception $e) { Z_Core::logError($e); } $this->e204(); } } // Display item switch ($this->queryParams['format']) { case 'atom': $this->responseXML = Zotero_Items::convertItemToAtom($item, $this->queryParams, $this->apiVersion, $this->permissions); break; case 'bib': echo Zotero_Cite::getBibliographyFromCitationServer(array($item), $this->queryParams['style'], $this->queryParams['css']); exit; case 'csljson': $json = Zotero_Cite::getJSONFromItems(array($item), true); if ($this->queryParams['pprint']) { header("Content-Type: text/plain"); $json = Zotero_Utilities::json_encode_pretty($json); } else { header("Content-Type: application/vnd.citationstyles.csl+json"); $json = json_encode($json); } echo $json; exit; default: $export = Zotero_Translate::doExport(array($item), $this->queryParams['format']); if ($this->queryParams['pprint']) { header("Content-Type: text/plain"); } else { header("Content-Type: " . $export['mimeType']); } echo $export['body']; exit; } } else { $this->allowMethods(array('GET', 'POST')); if (!$this->permissions->canAccess($this->objectLibraryID)) { $this->e403(); } $includeTrashed = false; $formatAsKeys = $this->queryParams['format'] == 'keys'; if ($this->scopeObject) { $this->allowMethods(array('GET', 'POST')); // If id, redirect to key URL if ($this->scopeObjectID) { $this->allowMethods(array('GET')); if (!in_array($this->scopeObject, array("collections", "tags"))) { $this->e400(); } $className = 'Zotero_' . ucwords($this->scopeObject); $obj = call_user_func(array($className, 'get'), $this->objectLibraryID, $this->scopeObjectID); if (!$obj) { $this->e404("Scope " . substr($this->scopeObject, 0, -1) . " not found"); } $base = call_user_func(array('Zotero_API', 'get' . substr(ucwords($this->scopeObject), 0, -1) . 'URI'), $obj); $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''; header("Location: " . $base . "/items" . $qs); exit; } switch ($this->scopeObject) { case 'collections': $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey); if (!$collection) { $this->e404("Collection not found"); } // Add items to collection if ($this->method == 'POST') { if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } Zotero_DB::beginTransaction(); $timestamp = Zotero_Libraries::updateTimestamps($this->objectLibraryID); Zotero_DB::registerTransactionTimestamp($timestamp); $itemKeys = explode(' ', $this->body); $itemIDs = array(); foreach ($itemKeys as $key) { try { $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $key); } catch (Exception $e) { if ($e->getCode() == Z_ERROR_OBJECT_LIBRARY_MISMATCH) { $item = false; } else { throw $e; } } if (!$item) { throw new Exception("Item '{$key}' not found in library", Z_ERROR_INVALID_INPUT); } if ($item->getSource()) { throw new Exception("Child items cannot be added to collections directly", Z_ERROR_INVALID_INPUT); } $itemIDs[] = $item->id; } $collection->addItems($itemIDs); Zotero_DB::commit(); $this->e204(); } $title = "Items in Collection ‘" . $collection->name . "’"; $itemIDs = $collection->getChildItems(); break; case 'tags': $this->allowMethods(array('GET')); $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $this->scopeObjectName); if (!$tagIDs) { $this->e404("Tag not found"); } $itemIDs = array(); $title = ''; foreach ($tagIDs as $tagID) { $tag = new Zotero_Tag(); $tag->libraryID = $this->objectLibraryID; $tag->id = $tagID; // Use a real tag name, in case case differs if (!$title) { $title = "Items of Tag ‘" . $tag->name . "’"; } $itemIDs = array_merge($itemIDs, $tag->getLinkedItems(true)); } $itemIDs = array_unique($itemIDs); break; default: throw new Exception("Invalid items scope object '{$this->scopeObject}'"); } } else { // Top-level items if ($this->subset == 'top') { $this->allowMethods(array('GET')); $title = "Top-Level Items"; $results = Zotero_Items::search($this->objectLibraryID, true, $this->queryParams, false, $formatAsKeys); } else { if ($this->subset == 'trash') { $this->allowMethods(array('GET')); $title = "Deleted Items"; $itemIDs = Zotero_Items::getDeleted($this->objectLibraryID, true); $includeTrashed = true; } else { if ($this->subset == 'children') { // If we have an id, make sure this isn't really an all-numeric key if ($this->objectID && strlen($this->objectID) == 8 && preg_match('/[0-9]{8}/', $this->objectID)) { $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectID); if ($item) { $this->objectKey = $this->objectID; unset($this->objectID); } } // If id, redirect to key URL if ($this->objectID) { $this->allowMethods(array('GET')); $item = Zotero_Items::get($this->objectLibraryID, $this->objectID); if (!$item) { $this->e404("Item not found"); } $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''; header("Location: " . Zotero_API::getItemURI($item) . '/children' . $qs); exit; } $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectKey); if (!$item) { $this->e404("Item not found"); } // Create new child items if ($this->method == 'POST') { if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } $obj = $this->jsonDecode($this->body); $keys = Zotero_Items::addFromJSON($obj, $this->objectLibraryID, $item, $this->userID); if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } $uri = Zotero_API::getItemURI($item) . "/children"; $queryString = "itemKey=" . urlencode(implode(",", $keys)) . "&content=json"; if ($this->apiKey) { $queryString .= "&key=" . $this->apiKey; } $uri .= "?" . $queryString; $this->responseCode = 201; $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, false); } // Display items $title = "Child Items of ‘" . $item->getDisplayTitle() . "’"; $notes = $item->getNotes(); $attachments = $item->getAttachments(); $itemIDs = array_merge($notes, $attachments); } else { // Create new items if ($this->method == 'POST') { if (!$this->permissions->canWrite($this->objectLibraryID)) { $this->e403("Write access denied"); } $obj = $this->jsonDecode($this->body); if (isset($obj->url)) { $response = Zotero_Items::addFromURL($obj, $this->objectLibraryID, $this->userID, $this->getTranslationToken()); if ($response instanceof stdClass) { header("Content-Type: application/json"); echo json_encode($response->select); $this->e300(); } else { if (is_int($response)) { switch ($response) { case 501: $this->e501("No translators found for URL"); break; default: $this->e500("Error translating URL"); } } else { $keys = $response; } } } else { $keys = Zotero_Items::addFromJSON($obj, $this->objectLibraryID, null, $this->userID); } if (!$keys) { throw new Exception("No items added"); } if ($cacheKey = $this->getWriteTokenCacheKey()) { Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime); } $uri = Zotero_API::getItemsURI($this->objectLibraryID); $queryString = "itemKey=" . urlencode(implode(",", $keys)) . "&content=json"; if ($this->apiKey) { $queryString .= "&key=" . $this->apiKey; } $uri .= "?" . $queryString; $this->responseCode = 201; $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, false); } $title = "Items"; $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, false, $formatAsKeys); } } } if (!empty($results)) { if ($formatAsKeys) { $responseKeys = $results['keys']; } else { $responseItems = $results['items']; } $totalResults = $results['total']; } } if ($this->queryParams['format'] == 'bib') { if (($itemIDs ? sizeOf($itemIDs) : $results['total']) > Zotero_API::$maxBibliographyItems) { $this->e413("Cannot generate bibliography with more than " . Zotero_API::$maxBibliographyItems . " items"); } } if ($itemIDs) { $this->queryParams['itemIDs'] = $itemIDs; $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $formatAsKeys); if ($formatAsKeys) { $responseKeys = $results['keys']; } else { $responseItems = $results['items']; } $totalResults = $results['total']; } else { if (!isset($results)) { if ($formatAsKeys) { $responseKeys = array(); } else { $responseItems = array(); } $totalResults = 0; } } // Remove notes if not user and not public for ($i = 0; $i < sizeOf($responseItems); $i++) { if ($responseItems[$i]->isNote() && !$this->permissions->canAccess($responseItems[$i]->libraryID, 'notes')) { array_splice($responseItems, $i, 1); $totalResults--; $i--; } } switch ($this->queryParams['format']) { case 'atom': $this->responseXML = Zotero_Atom::createAtomFeed($this->getFeedNamePrefix($this->objectLibraryID) . $title, $this->uri, $responseItems, $totalResults, $this->queryParams, $this->apiVersion, $this->permissions); break; case 'bib': echo Zotero_Cite::getBibliographyFromCitationServer($responseItems, $this->queryParams['style'], $this->queryParams['css']); exit; case 'csljson': $json = Zotero_Cite::getJSONFromItems($responseItems, true); if ($this->queryParams['pprint']) { header("Content-Type: text/plain"); $json = Zotero_Utilities::json_encode_pretty($json); } else { header("Content-Type: application/vnd.citationstyles.csl+json"); $json = json_encode($json); } echo $json; exit; case 'keys': if (!$formatAsKeys) { $responseKeys = array(); foreach ($responseItems as $item) { $responseKeys[] = $item->key; } } header("Content-Type: text/plain"); echo implode("\n", $responseKeys) . "\n"; exit; default: $export = Zotero_Translate::doExport($responseItems, $this->queryParams['format']); if ($this->queryParams['pprint']) { header("Content-Type: text/plain"); } else { header("Content-Type: " . $export['mimeType']); } echo $export['body']; exit; } } $this->end(); }
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\n\t\t\t\t\t\tstarted < (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\n\t\t\t\t\ttries>=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 { $xml = new SimpleXMLElement($row['xmldata']); $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'])); try { $sql = "INSERT INTO syncUploadProcessLog\n\t\t\t\t\t\t(userID, dataLength, processorHost, processDuration, totalDuration, error)\n\t\t\t\t\t\tVALUES (?,?,INET_ATON(?),?,?,?)"; Zotero_DB::query($sql, array($row['userID'], $row['dataLength'], $host, round((double) 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); } try { // Index new items Zotero_Processors::notifyProcessors('index'); } catch (Exception $e) { Z_Core::logError($e); } } 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 || $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; } 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 try { $serialized = serialize($e); } catch (Exception $e2) { //$id = substr(md5(uniqid(rand(), true)), 0, 10); //file_put_contents("/tmp/uploadError_$id", $e); $serialized = serialize(new Exception($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'])); try { $sql = "INSERT INTO syncUploadProcessLog\n\t\t\t\t\t\t(userID, dataLength, processorHost, processDuration, totalDuration, error)\n\t\t\t\t\t\tVALUES (?,?,INET_ATON(?),?,?,?)"; Zotero_DB::query($sql, array($row['userID'], $row['dataLength'], $host, round((double) 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; }
/** * Handle uploaded data, overwriting existing data */ public function upload() { $this->sessionCheck(); // Another session is either queued or writing — upload data won't be valid, // so client should wait and return to /updated with 'upload' flag Zotero_DB::beginTransaction(); if (Zotero_Sync::userIsReadLocked($this->userID) || Zotero_Sync::userIsWriteLocked($this->userID)) { Zotero_DB::commit(); $locked = $this->responseXML->addChild('locked'); $locked['wait'] = $this->getWaitTime($this->sessionID); $this->end(); } Zotero_DB::commit(); $this->clearWaitTime($this->sessionID); if (empty($_REQUEST['updateKey'])) { $this->error(400, 'INVALID_UPLOAD_DATA', 'Update key not provided'); } if ($_REQUEST['updateKey'] != Zotero_Users::getUpdateKey($this->userID)) { $this->e409("Server data has changed since last retrieval"); } // TODO: change to POST if (empty($_REQUEST['data'])) { $this->error(400, 'MISSING_UPLOAD_DATA', 'Uploaded data not provided'); } $xmldata =& $_REQUEST['data']; try { $doc = new DOMDocument(); $doc->loadXML($xmldata, LIBXML_PARSEHUGE); // For huge uploads, make sure notes aren't bigger than SimpleXML can parse if (strlen($xmldata) > 7000000) { $xpath = new DOMXPath($doc); $results = $xpath->query('/data/items/item/note[string-length(text()) > ' . Zotero_Notes::$MAX_NOTE_LENGTH . ']'); if ($results->length) { $noteElem = $results->item(0); $text = $noteElem->textContent; $libraryID = $noteElem->parentNode->getAttribute('libraryID'); $key = $noteElem->parentNode->getAttribute('key'); // UTF-8 (0xC2 0xA0) isn't trimmed by default $whitespace = chr(0x20) . chr(0x9) . chr(0xa) . chr(0xd) . chr(0x0) . chr(0xb) . chr(0xc2) . chr(0xa0); $excerpt = iconv("UTF-8", "UTF-8//IGNORE", Zotero_Notes::noteToTitle(trim($text), true)); $excerpt = trim($excerpt, $whitespace); // If tag-stripped version is empty, just return raw HTML if ($excerpt == '') { $excerpt = iconv("UTF-8", "UTF-8//IGNORE", preg_replace('/\\s+/', ' ', mb_substr(trim($text), 0, Zotero_Notes::$MAX_TITLE_LENGTH))); $excerpt = html_entity_decode($excerpt); $excerpt = trim($excerpt, $whitespace); } $msg = "=Note '" . $excerpt . "...' too long"; if ($key) { $msg .= " for item '" . $libraryID . "/" . $key . "'"; } throw new Exception($msg, Z_ERROR_NOTE_TOO_LONG); } } } catch (Exception $e) { $this->handleUploadError($e, $xmldata); } function relaxNGErrorHandler($errno, $errstr) { //Z_Core::logError($errstr); } set_error_handler('relaxNGErrorHandler'); set_time_limit(60); if (!$doc->relaxNGValidate(Z_ENV_MODEL_PATH . 'relax-ng/upload.rng')) { $id = substr(md5(uniqid(rand(), true)), 0, 10); $str = date("D M j G:i:s T Y") . "\n"; $str .= "IP address: " . $_SERVER['REMOTE_ADDR'] . "\n"; if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) { $str .= "Version: " . $_SERVER['HTTP_X_ZOTERO_VERSION'] . "\n"; } $str .= "Error: RELAX NG validation failed\n\n"; $str .= $xmldata; if (!file_put_contents(Z_CONFIG::$SYNC_ERROR_PATH . $id, $str)) { error_log("Unable to save error report to " . Z_CONFIG::$SYNC_ERROR_PATH . $id); } $this->error(500, 'INVALID_UPLOAD_DATA', "Uploaded data not well-formed (Report ID: {$id})"); } restore_error_handler(); try { $xml = simplexml_import_dom($doc); $queue = true; if (Z_ENV_TESTING_SITE && !empty($_GET['noqueue'])) { $queue = false; } if ($queue) { $affectedLibraries = Zotero_Sync::parseAffectedLibraries($xmldata); // Relations-only uploads don't have affected libraries if (!$affectedLibraries) { $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($this->userID)); } Zotero_Sync::queueUpload($this->userID, $this->sessionID, $xmldata, $affectedLibraries); try { Zotero_Processors::notifyProcessors('upload'); Zotero_Processors::notifyProcessors('error'); usleep(750000); } catch (Exception $e) { Z_Core::logError($e); } // Give processor a chance to finish while we're still here $this->uploadstatus(); } else { set_time_limit(210); $timestamp = Zotero_Sync::processUpload($this->userID, $xml); $this->responseXML['timestamp'] = $timestamp; $this->responseXML->addChild('uploaded'); $this->end(); } } catch (Exception $e) { $this->handleUploadError($e, $xmldata); } }
private function unregister() { Zotero_Processors::unregister($this->mode, $this->addr, $this->port); }
/** * Handle uploaded data, overwriting existing data */ public function upload() { $this->sessionCheck(); // Another session is either queued or writing — upload data won't be valid, // so client should wait and return to /updated with 'upload' flag Zotero_DB::beginTransaction(); if (Zotero_Sync::userIsReadLocked($this->userID) || Zotero_Sync::userIsWriteLocked($this->userID)) { Zotero_DB::commit(); $locked = $this->responseXML->addChild('locked'); $locked['wait'] = $this->getWaitTime($this->sessionID); $this->end(); } Zotero_DB::commit(); $this->clearWaitTime($this->sessionID); if (empty($_REQUEST['updateKey'])) { $this->error(400, 'INVALID_UPLOAD_DATA', 'Update key not provided'); } if ($_REQUEST['updateKey'] != Zotero_Users::getUpdateKey($this->userID)) { $this->e409("Server data has changed since last retrieval"); } // TODO: change to POST if (empty($_REQUEST['data'])) { $this->error(400, 'MISSING_UPLOAD_DATA', 'Uploaded data not provided'); } $xmldata =& $_REQUEST['data']; $doc = new DOMDocument(); $doc->loadXML($xmldata); function relaxNGErrorHandler($errno, $errstr) { //Z_Core::logError($errstr); } set_error_handler('relaxNGErrorHandler'); set_time_limit(60); if (!$doc->relaxNGValidate(Z_ENV_MODEL_PATH . 'relax-ng/upload.rng')) { $id = substr(md5(uniqid(rand(), true)), 0, 10); $str = date("D M j G:i:s T Y") . "\n"; $str .= "IP address: " . $_SERVER['REMOTE_ADDR'] . "\n"; if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) { $str .= "Version: " . $_SERVER['HTTP_X_ZOTERO_VERSION'] . "\n"; } $str .= "Error: RELAX NG validation failed\n\n"; $str .= $xmldata; file_put_contents(Z_CONFIG::$SYNC_ERROR_PATH . $id, $str); $this->error(500, 'INVALID_UPLOAD_DATA', "Uploaded data not well-formed (Report ID: {$id})"); } restore_error_handler(); try { $xml = simplexml_import_dom($doc); $queue = true; if (Z_ENV_TESTING_SITE && !empty($_GET['noqueue'])) { $queue = false; } if ($queue) { $affectedLibraries = Zotero_Sync::parseAffectedLibraries($xmldata); // Relations-only uploads don't have affected libraries if (!$affectedLibraries) { $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($this->userID)); } Zotero_Sync::queueUpload($this->userID, $this->sessionID, $xmldata, $affectedLibraries); try { Zotero_Processors::notifyProcessors('upload'); Zotero_Processors::notifyProcessors('error'); usleep(750000); } catch (Exception $e) { Z_Core::logError($e); } // Give processor a chance to finish while we're still here $this->uploadstatus(); } else { set_time_limit(210); $timestamp = Zotero_Sync::processUpload($this->userID, $xml); $this->responseXML['timestamp'] = $timestamp; $this->responseXML->addChild('uploaded'); Zotero_Processors::notifyProcessors('index'); $this->end(); } } catch (Exception $e) { $this->handleUploadError($e, $xmldata); } }