Exemple #1
0
 private static function getNext($table)
 {
     $sql = "REPLACE INTO {$table} (stub) VALUES ('a')";
     if (Z_Core::probability(2)) {
         try {
             Zotero_ID_DB_1::query($sql);
             $id = Zotero_ID_DB_1::valueQuery("SELECT LAST_INSERT_ID()");
         } catch (Exception $e) {
             Z_Core::logError("Error accessing ID server 1");
             Zotero_ID_DB_2::query($sql);
             $id = Zotero_ID_DB_2::valueQuery("SELECT LAST_INSERT_ID()");
         }
     } else {
         try {
             Zotero_ID_DB_2::query($sql);
             $id = Zotero_ID_DB_2::valueQuery("SELECT LAST_INSERT_ID()");
         } catch (Exception $e) {
             Z_Core::logError("Error accessing ID server 2");
             Zotero_ID_DB_1::query($sql);
             $id = Zotero_ID_DB_1::valueQuery("SELECT LAST_INSERT_ID()");
         }
     }
     if (!$id || !is_int($id)) {
         throw new Exception("Invalid id {$id}");
     }
     return $id;
 }
 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 getAllAdvanced($userID = false, $params = array(), $permissions = null)
 {
     $buffer = 20;
     $maxTimes = 3;
     $groups = array();
     $start = !empty($params['start']) ? $params['start'] : 0;
     $limit = !empty($params['limit']) ? $params['limit'] + $buffer : false;
     $totalResults = null;
     $times = 0;
     while (true) {
         if ($times > 0) {
             Z_Core::logError('Getting more groups in Zotero_Groups::getAllAdvanced()');
         }
         $sql = "SELECT SQL_CALC_FOUND_ROWS G.groupID, GUO.userID AS ownerUserID FROM groups G\n\t\t\t\t\tJOIN groupUsers GUO ON (G.groupID=GUO.groupID AND GUO.role='owner') ";
         $sqlParams = array();
         if ($userID) {
             $sql .= "JOIN groupUsers GUA ON (G.groupID=GUA.groupID) WHERE GUA.userID=? ";
             $sqlParams[] = $userID;
         }
         $paramSQL = "";
         $includeEmpty = false;
         if (!empty($params['q'])) {
             if (!is_array($params['q'])) {
                 $params['q'] = array($params['q']);
             }
             foreach ($params['q'] as $q) {
                 $field = explode(":", $q);
                 if (sizeOf($field) == 2) {
                     switch ($field[0]) {
                         case 'slug':
                             $includeEmpty = true;
                             break;
                         default:
                             throw new Exception("Cannot search by group field '{$field[0]}'", Z_ERROR_INVALID_GROUP_TYPE);
                     }
                     $paramSQL .= "AND " . $field[0];
                     // If first character is '-', negate
                     $paramSQL .= $field[0][0] == '-' ? '!' : '';
                     $paramSQL .= "=? ";
                     $sqlParams[] = $field[1];
                 } else {
                     $paramSQL .= "AND name LIKE ? ";
                     $sqlParams[] = "%{$q}%";
                 }
             }
         }
         if (!$userID) {
             if ($includeEmpty) {
                 $sql .= "WHERE 1 ";
             } else {
                 // Don't include groups that have never had items
                 $sql .= "JOIN libraries L ON (G.libraryID=L.libraryID)\n\t\t\t\t\t\t\tWHERE L.lastUpdated != '0000-00-00 00:00:00' ";
             }
         }
         $sql .= $paramSQL;
         if (!empty($params['fq'])) {
             if (!is_array($params['fq'])) {
                 $params['fq'] = array($params['fq']);
             }
             foreach ($params['fq'] as $fq) {
                 $facet = explode(":", $fq);
                 if (sizeOf($facet) == 2 && preg_match('/-?GroupType/', $facet[0])) {
                     switch ($facet[1]) {
                         case 'PublicOpen':
                         case 'PublicClosed':
                         case 'Private':
                             break;
                         default:
                             throw new Exception("Invalid group type '{$facet[1]}'", Z_ERROR_INVALID_GROUP_TYPE);
                     }
                     $sql .= "AND type";
                     // If first character is '-', negate
                     $sql .= $facet[0][0] == '-' ? '!' : '';
                     $sql .= "=? ";
                     $sqlParams[] = $facet[1];
                 }
             }
         }
         if (!empty($params['order'])) {
             $order = $params['order'];
             if ($order == 'title') {
                 $order = 'name';
             }
             $sql .= "ORDER BY {$order}";
             if (!empty($params['sort'])) {
                 $sql .= " " . $params['sort'] . " ";
             }
         }
         // Set limit higher than the actual limit, in case some groups are
         // removed during access checks
         //
         // Actual limiting is done below
         if ($limit) {
             $sql .= "LIMIT ?, ?";
             $sqlParams[] = $start;
             $sqlParams[] = $limit;
         }
         $rows = Zotero_DB::query($sql, $sqlParams);
         if (!$rows) {
             break;
         }
         if (!$totalResults) {
             $foundRows = Zotero_DB::valueQuery("SELECT FOUND_ROWS()");
             $totalResults = $foundRows;
         }
         // Include only groups with non-banned owners
         $owners = array();
         foreach ($rows as $row) {
             $owners[] = $row['ownerUserID'];
         }
         $owners = Zotero_Users::getValidUsers($owners);
         $ids = array();
         foreach ($rows as $row) {
             if (!in_array($row['ownerUserID'], $owners)) {
                 $totalResults--;
                 continue;
             }
             $ids[] = $row['groupID'];
         }
         $batchStartPos = sizeOf($groups);
         foreach ($ids as $id) {
             $group = Zotero_Groups::get($id, true);
             $groups[] = $group;
         }
         // Remove groups that can't be accessed
         if ($permissions) {
             for ($i = $batchStartPos; $i < sizeOf($groups); $i++) {
                 $libraryID = (int) $groups[$i]->libraryID;
                 // TEMP: casting shouldn't be necessary
                 if (!$permissions->canAccess($libraryID)) {
                     array_splice($groups, $i, 1);
                     $i--;
                     $totalResults--;
                 }
             }
         }
         $times++;
         if ($times == $maxTimes) {
             Z_Core::logError('Too many queries in Zotero_Groups::getAllAdvanced()');
             break;
         }
         if (empty($params['limit'])) {
             break;
         }
         // If we have enough groups to fill the limit, stop
         if (sizeOf($groups) > $params['limit']) {
             break;
         }
         // If there no more rows, stop
         if ($start + sizeOf($rows) == $foundRows) {
             break;
         }
         // This shouldn't happen
         if ($start + sizeOf($rows) > $foundRows) {
             Z_Core::logError('More rows than $foundRows in Zotero_Groups::getAllAdvanced()');
         }
         $start = $start + sizeOf($rows);
         // Get number we still need plus the buffer or all remaining, whichever is lower
         $limit = min($params['limit'] - sizeOf($groups) + $buffer, $foundRows - $start);
     }
     // TODO: generate previous start value
     if (!$groups) {
         return array('groups' => array(), 'totalResults' => 0);
     }
     // Fake limiting -- we can't just use SQL limit because
     // some groups might be inaccessible
     if (!empty($params['limit'])) {
         $groups = array_slice($groups, 0, $params['limit']);
     }
     $results = array('groups' => $groups, 'totalResults' => $totalResults);
     return $results;
 }
Exemple #4
0
 public static function getInstitutionalUserQuota($userID)
 {
     // TODO: config
     $dev = Z_ENV_TESTING_SITE ? "_test" : "";
     $databaseName = "zotero_www{$dev}";
     // Get maximum institutional quota by e-mail domain
     $sql = "SELECT IFNULL(MAX(storageQuota), 0) FROM {$databaseName}.users_email\n\t\t\t\tJOIN {$databaseName}.storage_institutions ON (SUBSTRING_INDEX(email, '@', -1)=domain)\n\t\t\t\tWHERE userID=?";
     try {
         $institutionalDomainQuota = Zotero_WWW_DB_2::valueQuery($sql, $userID);
     } catch (Exception $e) {
         Z_Core::logError("WARNING: {$e} -- retrying on primary");
         $institutionalDomainQuota = Zotero_WWW_DB_1::valueQuery($sql, $userID);
     }
     // Get maximum institutional quota by e-mail address
     $sql = "SELECT IFNULL(MAX(storageQuota), 0) FROM {$databaseName}.users_email\n\t\t\t\tJOIN {$databaseName}.storage_institution_email USING (email)\n\t\t\t\tJOIN {$databaseName}.storage_institutions USING (institutionID)\n\t\t\t\tWHERE userID=?";
     try {
         $institutionalEmailQuota = Zotero_WWW_DB_2::valueQuery($sql, $userID);
     } catch (Exception $e) {
         Z_Core::logError("WARNING: {$e} -- retrying on primary");
         $institutionalEmailQuota = Zotero_WWW_DB_1::valueQuery($sql, $userID);
     }
     $quota = max($institutionalDomainQuota, $institutionalEmailQuota);
     return $quota ? $quota : false;
 }
 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();
 }
Exemple #6
0
 /**
  * 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;
 }
Exemple #7
0
 public function erase()
 {
     if (!$this->loaded) {
         Z_Core::debug("Not deleting unloaded group {$this->id}");
         return;
     }
     Zotero_DB::beginTransaction();
     $userIDs = self::getUsers();
     $this->logGroupLibraryRemoval();
     Zotero_Libraries::deleteCachedData($this->libraryID);
     Zotero_Libraries::clearAllData($this->libraryID);
     $sql = "DELETE FROM shardLibraries WHERE libraryID=?";
     $deleted = Zotero_DB::query($sql, $this->libraryID, Zotero_Shards::getByLibraryID($this->libraryID));
     if (!$deleted) {
         throw new Exception("Group not deleted");
     }
     $sql = "DELETE FROM libraries WHERE libraryID=?";
     $deleted = Zotero_DB::query($sql, $this->libraryID);
     if (!$deleted) {
         throw new Exception("Group not deleted");
     }
     // Delete key permissions for this library, and then delete any keys
     // that had no other permissions
     $sql = "SELECT keyID FROM keyPermissions WHERE libraryID=?";
     $keyIDs = Zotero_DB::columnQuery($sql, $this->libraryID);
     if ($keyIDs) {
         $sql = "DELETE FROM keyPermissions WHERE libraryID=?";
         Zotero_DB::query($sql, $this->libraryID);
         $sql = "DELETE K FROM `keys` K LEFT JOIN keyPermissions KP USING (keyID)\n\t\t\t\t\tWHERE keyID IN (" . implode(', ', array_fill(0, sizeOf($keyIDs), '?')) . ") AND KP.keyID IS NULL";
         Zotero_DB::query($sql, $keyIDs);
     }
     // If group is locked by a sync, flag group for a timestamp update
     // once the sync is done so that the uploading user gets the change
     try {
         foreach ($userIDs as $userID) {
             if ($syncUploadQueueID = Zotero_Sync::getUploadQueueIDByUserID($userID)) {
                 Zotero_Sync::postWriteLog($syncUploadQueueID, 'group', $this->id, 'delete');
             }
         }
     } catch (Exception $e) {
         Z_Core::logError($e);
     }
     Zotero_Notifier::trigger('delete', 'library', $this->libraryID);
     Zotero_DB::commit();
     $this->erased = true;
 }
Exemple #8
0
 public static function getAllAdvanced($userID = false, $params = array(), $permissions = null)
 {
     $buffer = 20;
     $maxTimes = 3;
     $groups = array();
     $start = !empty($params['start']) ? $params['start'] : 0;
     $limit = !empty($params['limit']) ? $params['limit'] + $buffer : false;
     $totalResults = null;
     $times = 0;
     while (true) {
         if ($times > 0) {
             Z_Core::logError('Getting more groups in Zotero_Groups::getAllAdvanced()');
         }
         $calcFoundRows = !$totalResults;
         $cacheFoundRows = $calcFoundRows && !$userID;
         // If we don't yet have a row count and this isn't a user-specific search,
         // try to get a cached row count.
         if ($cacheFoundRows) {
             $foundRowsCacheKey = self::getCacheComponentFromParam($params, 'q') . "," . self::getCacheComponentFromParam($params, 'fq');
             $foundRowsTTL = 180;
             $foundRowsLockTTL = 10;
             $foundRowsRealTTL = 3600;
             $obj = Z_Core::$MC->get($foundRowsCacheKey);
             if ($obj) {
                 $foundRows = $obj['rows'];
                 $exp = $obj['exp'];
                 // If count was found but is past the expiration time, check if another
                 // request is getting the row count, and fetch it if not
                 if ($exp < time()) {
                     if (!Z_Core::$MC->add($foundRowsCacheKey . "Lock", true, $foundRowsLockTTL)) {
                         $calcFoundRows = false;
                     }
                 } else {
                     $calcFoundRows = false;
                 }
             }
         }
         $sql = "SELECT " . ($calcFoundRows ? "SQL_CALC_FOUND_ROWS " : "") . "G.groupID, GUO.userID AS ownerUserID FROM groups G " . "JOIN groupUsers GUO ON (G.groupID=GUO.groupID AND GUO.role='owner') ";
         $sqlParams = array();
         if ($userID) {
             $sql .= "JOIN groupUsers GUA ON (G.groupID=GUA.groupID) WHERE GUA.userID=? ";
             $sqlParams[] = $userID;
         }
         $paramSQL = "";
         $includeEmpty = false;
         if (!empty($params['q'])) {
             if (!is_array($params['q'])) {
                 $params['q'] = array($params['q']);
             }
             foreach ($params['q'] as $q) {
                 $field = explode(":", $q);
                 if (sizeOf($field) == 2) {
                     switch ($field[0]) {
                         case 'slug':
                             $includeEmpty = true;
                             break;
                         default:
                             throw new Exception("Cannot search by group field '{$field[0]}'", Z_ERROR_INVALID_GROUP_TYPE);
                     }
                     $paramSQL .= "AND " . $field[0];
                     // If first character is '-', negate
                     $paramSQL .= $field[0][0] == '-' ? '!' : '';
                     $paramSQL .= "=? ";
                     $sqlParams[] = $field[1];
                 } else {
                     $paramSQL .= "AND name LIKE ? ";
                     $sqlParams[] = "%{$q}%";
                 }
             }
         }
         if (!$userID) {
             if ($includeEmpty) {
                 $sql .= "WHERE 1 ";
             } else {
                 // Don't include groups that have never had items
                 $sql .= "JOIN libraries L ON (G.libraryID=L.libraryID)\n\t\t\t\t\t\t\tWHERE L.lastUpdated != '0000-00-00 00:00:00' ";
             }
         }
         $sql .= $paramSQL;
         if (!empty($params['fq'])) {
             if (!is_array($params['fq'])) {
                 $params['fq'] = array($params['fq']);
             }
             foreach ($params['fq'] as $fq) {
                 $facet = explode(":", $fq);
                 if (sizeOf($facet) == 2 && preg_match('/-?GroupType/', $facet[0])) {
                     switch ($facet[1]) {
                         case 'PublicOpen':
                         case 'PublicClosed':
                         case 'Private':
                             break;
                         default:
                             throw new Exception("Invalid group type '{$facet[1]}'", Z_ERROR_INVALID_GROUP_TYPE);
                     }
                     $sql .= "AND type";
                     // If first character is '-', negate
                     $sql .= $facet[0][0] == '-' ? '!' : '';
                     $sql .= "=? ";
                     $sqlParams[] = $facet[1];
                 }
             }
         }
         if (!empty($params['sort'])) {
             $order = $params['sort'];
             if ($order == 'title') {
                 $order = 'name';
             }
             $sql .= "ORDER BY {$order}";
             if (!empty($params['direction'])) {
                 $sql .= " " . $params['direction'] . " ";
             }
         }
         // Limit is set $buffer higher than the actual limit, in case some groups are
         // removed during access checks
         //
         // Actual limiting is done below
         if ($limit) {
             $sql .= "LIMIT ?, ?";
             $sqlParams[] = $start;
             $sqlParams[] = $limit;
         }
         $rows = Zotero_DB::query($sql, $sqlParams);
         if (!$rows) {
             break;
         }
         if (is_null($totalResults)) {
             if ($calcFoundRows) {
                 $foundRows = Zotero_DB::valueQuery("SELECT FOUND_ROWS()");
                 // Cache found rows count, and store earlier expiration time so that one
                 // request can trigger a recalculation before cached value expires
                 if ($cacheFoundRows) {
                     Z_Core::$MC->set($foundRowsCacheKey, ['rows' => $foundRows, 'exp' => time() + $foundRowsTTL], $foundRowsRealTTL);
                 }
             }
             $totalResults = $foundRows;
         }
         // Include only groups with non-banned owners
         $owners = array();
         foreach ($rows as $row) {
             $owners[] = $row['ownerUserID'];
         }
         $owners = Zotero_Users::getValidUsers($owners);
         $ids = array();
         foreach ($rows as $row) {
             if (!in_array($row['ownerUserID'], $owners)) {
                 $totalResults--;
                 continue;
             }
             $ids[] = $row['groupID'];
         }
         $batchStartPos = sizeOf($groups);
         foreach ($ids as $id) {
             $group = Zotero_Groups::get($id, true);
             $groups[] = $group;
         }
         // Remove groups that can't be accessed
         if ($permissions) {
             for ($i = $batchStartPos; $i < sizeOf($groups); $i++) {
                 if (!$permissions->canAccess($groups[$i]->libraryID, 'view')) {
                     array_splice($groups, $i, 1);
                     $i--;
                     $totalResults--;
                 }
             }
         }
         $times++;
         if ($times == $maxTimes) {
             Z_Core::logError('Too many queries in Zotero_Groups::getAllAdvanced()');
             break;
         }
         if (empty($params['limit'])) {
             break;
         }
         // If we have enough groups to fill the limit, stop
         if (sizeOf($groups) > $params['limit']) {
             break;
         }
         // If there no more rows, stop
         if ($start + sizeOf($rows) >= $foundRows) {
             break;
         }
         $start = $start + sizeOf($rows);
         // Get number we still need plus the buffer or all remaining, whichever is lower
         $limit = min($params['limit'] - sizeOf($groups) + $buffer, $foundRows - $start);
     }
     // TODO: generate previous start value
     if (!$groups) {
         return array('results' => array(), 'total' => 0);
     }
     // Fake limiting -- we can't just use SQL limit because
     // some groups might be inaccessible
     if (!empty($params['limit'])) {
         $groups = array_slice($groups, 0, $params['limit']);
     }
     $results = array('results' => $groups, 'total' => $totalResults);
     return $results;
 }
Exemple #9
0
 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;
     } else {
         $obj->items = $response;
     }
     return $obj;
 }
Exemple #10
0
 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\n\t\t\t   WHERE username = ?\n\t\t\t   UNION\n\t\t\t   SELECT userID, username, password AS hash FROM {$databaseName}.users\n\t\t\t   WHERE email = ?\n\t\t\t   ORDER BY username = ? DESC";
         $params = [$username, $username, $username];
     }
     try {
         $retry = true;
         $rows = Zotero_WWW_DB_2::query($sql, $params);
         if (!$rows) {
             $retry = false;
             $rows = Zotero_WWW_DB_1::query($sql, $params);
         }
     } catch (Exception $e) {
         if ($retry) {
             Z_Core::logError("WARNING: {$e} -- retrying on primary");
             $rows = Zotero_WWW_DB_1::query($sql, $params);
         }
     }
     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'];
 }
Exemple #11
0
 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;
 }
 public function set($key, $val, $exptime = 0)
 {
     if ($this->disabled) {
         return false;
     }
     if (!$key) {
         throw new Exception("Memcached key not provided");
     }
     if ($this->queuing) {
         // If this is already set, mark the previous position for skipping when committing
         if (isset($this->queueKeyPos[$key])) {
             $pos = $this->queueKeyPos[$key];
             $this->queue[$pos]['skip'] = true;
         }
         $this->queue[] = array('op' => 'set', 'key' => $key, 'exp' => $exptime);
         $this->queueValues[$key] = $val;
         // Store the position in case we need to clear later
         $this->queueKeyPos[$key] = sizeOf($this->queue) - 1;
         return true;
     }
     $success = $this->client->set($this->prefix . $key, $val, null, $exptime);
     if (!$success && !$this->errorLogged) {
         Z_Core::logError("Setting memcache value failed (key {$key}, value {$val})");
         $this->errorLogged = true;
     }
     return $success;
 }
 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;
 }
 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_' . sha1($username . $salt . $password);
     $userID = Z_Core::$MC->get($cacheKey);
     if ($userID) {
         return $userID;
     }
     // Query the database looking for a salted SHA1 password
     $passwordSha1 = sha1($salt . $password);
     if (!$isEmailAddress) {
         // Try username
         $sql = "SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t   WHERE username = ? AND password = ?\n\t\t\t\t\t   LIMIT 1";
         $params = array($username, $passwordSha1);
         try {
             $retry = true;
             $row = Zotero_WWW_DB_2::rowQuery($sql, $params);
             if (!$row) {
                 $retry = false;
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         } catch (Exception $e) {
             if ($retry) {
                 Z_Core::logError("WARNING: {$e} -- retrying on primary");
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         }
     } else {
         // Try both username and e-mail address
         $sql = "SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t   WHERE username = ? AND password = ?\n\t\t\t\t\t   UNION\n\t\t\t\t\t   SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t   WHERE email = ? AND password = ?\n\t\t\t\t\t   ORDER BY username = ? DESC\n\t\t\t\t\t   LIMIT 1";
         $params = array($username, $passwordSha1, $username, $passwordSha1, $username);
         try {
             $retry = true;
             $row = Zotero_WWW_DB_2::rowQuery($sql, $params);
             if (!$row) {
                 $retry = false;
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         } catch (Exception $e) {
             if ($retry) {
                 Z_Core::logError("WARNING: {$e} -- retrying on primary");
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         }
     }
     // If not found, check for an MD5 password
     if (!$row) {
         $passwordMd5 = md5($password);
         if (!$isEmailAddress) {
             // Try username
             $sql = "SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t\t   WHERE username = ? AND password = ? LIMIT 1";
             $params = array($username, $passwordMd5);
             try {
                 $row = Zotero_WWW_DB_2::rowQuery($sql, $params);
             } catch (Exception $e) {
                 Z_Core::logError("WARNING: {$e} -- retrying on primary");
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         } else {
             // Try both username and e-mail address
             $sql = "SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t\t   WHERE username = ? AND password = ?\n\t\t\t\t\t\t   UNION\n\t\t\t\t\t\t   SELECT userID, username FROM {$databaseName}.users\n\t\t\t\t\t\t   WHERE email = ? AND password = ?\n\t\t\t\t\t\t   ORDER BY username = ? DESC\n\t\t\t\t\t\t   LIMIT 1";
             $params = array($username, $passwordMd5, $username, $passwordMd5, $username);
             try {
                 $row = Zotero_WWW_DB_2::rowQuery($sql, $params);
             } catch (Exception $e) {
                 Z_Core::logError("WARNING: {$e} -- retrying on primary");
                 $row = Zotero_WWW_DB_1::rowQuery($sql, $params);
             }
         }
     }
     if (!$row) {
         return false;
     }
     self::updateUser($row['userID'], $row['username']);
     Z_Core::$MC->set($cacheKey, $row['userID'], 60);
     return $row['userID'];
 }
Exemple #15
0
 public function set($key, $val, $exptime = 0)
 {
     if ($this->disabled) {
         return false;
     }
     if (!$key) {
         throw new Exception("Memcached key not provided");
     }
     if ($this->queuing) {
         // If this is already set, mark the previous position for skipping when committing
         if (isset($this->queueKeyPos[$key])) {
             $pos = $this->queueKeyPos[$key];
             $this->queue[$pos]['skip'] = true;
         }
         $this->queue[] = array('op' => 'set', 'key' => $key, 'exp' => $exptime);
         $this->queueValues[$key] = $val;
         // Store the position in case we need to clear later
         $this->queueKeyPos[$key] = sizeOf($this->queue) - 1;
         return true;
     }
     $t = microtime(true);
     $success = $this->client->set($key, $val, $exptime);
     $this->requestTime += microtime(true) - $t;
     if (!$success && !$this->errorLogged) {
         Z_Core::logError("Setting memcache value failed for key {$key}: " . $this->client->getResultMessage() . " (" . $this->client->getServerByKey($key)['host'] . ")");
         $this->errorLogged = true;
     }
     return $success;
 }
Exemple #16
0
 private function handleUploadError(Exception $e, $xmldata)
 {
     $msg = $e->getMessage();
     if ($msg[0] == '=') {
         $msg = substr($msg, 1);
         $explicit = true;
         // TODO: more specific error messages
     } else {
         $explicit = false;
     }
     switch ($e->getCode()) {
         case Z_ERROR_TAG_TOO_LONG:
         case Z_ERROR_COLLECTION_TOO_LONG:
             break;
         default:
             Z_Core::logError($msg);
     }
     if (!$explicit && Z_ENV_TESTING_SITE) {
         switch ($e->getCode()) {
             case Z_ERROR_COLLECTION_NOT_FOUND:
             case Z_ERROR_CREATOR_NOT_FOUND:
             case Z_ERROR_ITEM_NOT_FOUND:
             case Z_ERROR_TAG_TOO_LONG:
             case Z_ERROR_LIBRARY_ACCESS_DENIED:
             case Z_ERROR_TAG_LINKED_ITEM_NOT_FOUND:
                 break;
             default:
                 throw $e;
         }
         $id = 'N/A';
     } else {
         $id = substr(md5(uniqid(rand(), true)), 0, 8);
         $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 .= $msg;
         switch ($e->getCode()) {
             // Don't log uploaded data for some errors
             case Z_ERROR_TAG_TOO_LONG:
             case Z_ERROR_FIELD_TOO_LONG:
             case Z_ERROR_NOTE_TOO_LONG:
             case Z_ERROR_COLLECTION_TOO_LONG:
                 break;
             default:
                 $str .= "\n\n" . $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);
         }
     }
     Zotero_DB::rollback(true);
     switch ($e->getCode()) {
         case Z_ERROR_LIBRARY_ACCESS_DENIED:
             preg_match('/[Ll]ibrary ([0-9]+)/', $e->getMessage(), $matches);
             $libraryID = $matches ? $matches[1] : null;
             $this->error(400, 'LIBRARY_ACCESS_DENIED', "Cannot make changes to library (Report ID: {$id})", array('libraryID' => $libraryID));
             break;
         case Z_ERROR_ITEM_NOT_FOUND:
         case Z_ERROR_COLLECTION_NOT_FOUND:
         case Z_ERROR_CREATOR_NOT_FOUND:
             error_log($e);
             $this->error(500, "FULL_SYNC_REQUIRED", "Please perform a full sync in the Sync->Reset pane of the Zotero preferences. (Report ID: {$id})");
             break;
         case Z_ERROR_TAG_TOO_LONG:
             $message = $e->getMessage();
             preg_match("/Tag '(.+)' too long/s", $message, $matches);
             if ($matches) {
                 $name = $matches[1];
                 $this->error(400, "TAG_TOO_LONG", "Tag '" . mb_substr($name, 0, 50) . "…' too long", array(), array("tag" => $name));
             }
             break;
         case Z_ERROR_COLLECTION_TOO_LONG:
             $message = $e->getMessage();
             preg_match("/Collection '(.+)' too long/s", $message, $matches);
             if ($matches) {
                 $name = $matches[1];
                 $this->error(400, "COLLECTION_TOO_LONG", "Collection '" . mb_substr($name, 0, 50) . "…' too long", array(), array("collection" => $name));
             }
             break;
         case Z_ERROR_NOTE_TOO_LONG:
             preg_match("/Note '(.+)' too long(?: for item '(.+)\\/(.+)')?/s", $msg, $matches);
             if ($matches) {
                 $name = $matches[1];
                 $libraryID = false;
                 if (isset($matches[2])) {
                     $libraryID = (int) $matches[2];
                     $itemKey = $matches[3];
                     if (Zotero_Libraries::getType($libraryID) == 'group') {
                         $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
                         $group = Zotero_Groups::get($groupID);
                         $libraryName = $group->name;
                     } else {
                         $libraryName = false;
                     }
                 } else {
                     $itemKey = '';
                 }
                 $showNoteKey = false;
                 if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) {
                     require_once '../model/ToolkitVersionComparator.inc.php';
                     $showNoteKey = ToolkitVersionComparator::compare($_SERVER['HTTP_X_ZOTERO_VERSION'], "4.0.27") < 0;
                 }
                 if ($showNoteKey) {
                     $this->error(400, "ERROR_PROCESSING_UPLOAD_DATA", "The note '" . mb_substr($name, 0, 50) . "…' in " . ($libraryName === false ? "your library " : "the group '{$libraryName}' ") . "is too long to sync to zotero.org.\n\n" . "Search for the excerpt above or copy and paste " . "'{$itemKey}' into the Zotero search bar. " . "Shorten the note, or delete it and empty the Zotero " . "trash, and then try syncing again.");
                 } else {
                     $this->error(400, "NOTE_TOO_LONG", "The note '" . mb_substr($name, 0, 50) . "…' in " . ($libraryName === false ? "your library " : "the group '{$libraryName}' ") . "is too long to sync to zotero.org.\n\n" . "Shorten the note, or delete it and empty the Zotero " . "trash, and then try syncing again.", [], $libraryID ? ["item" => $libraryID . "/" . $itemKey] : []);
                 }
             }
             break;
         case Z_ERROR_FIELD_TOO_LONG:
             preg_match("/(.+) field value '(.+)\\.\\.\\.' too long(?: for item '(.+)')?/s", $msg, $matches);
             if ($matches) {
                 $fieldName = $matches[1];
                 $value = $matches[2];
                 if (isset($matches[3])) {
                     $parts = explode("/", $matches[3]);
                     $libraryID = (int) $parts[0];
                     $itemKey = $parts[1];
                     if (Zotero_Libraries::getType($libraryID) == 'group') {
                         $groupID = Zotero_Groups::getGroupIDFromLibraryID($libraryID);
                         $group = Zotero_Groups::get($groupID);
                         $libraryName = "the group '" . $group->name . "'";
                     } else {
                         $libraryName = "your personal library";
                     }
                 } else {
                     $libraryName = "one of your libraries";
                     $itemKey = false;
                 }
                 $this->error(400, "ERROR_PROCESSING_UPLOAD_DATA", "The {$fieldName} field value '{$value}…' in {$libraryName} is " . "too long to sync to zotero.org.\n\n" . "Search for the excerpt above " . ($itemKey === false ? "using " : "or copy and paste " . "'{$itemKey}' into ") . "the Zotero search bar. " . "Shorten the field, or delete the item and empty the " . "Zotero trash, and then try syncing again.");
             }
             break;
         case Z_ERROR_ARRAY_SIZE_MISMATCH:
             $this->error(400, 'DATABASE_TOO_LARGE', "Databases of this size cannot yet be synced. Please check back soon. (Report ID: {$id})");
             break;
         case Z_ERROR_TAG_LINKED_ITEM_NOT_FOUND:
             $this->error(400, 'WRONG_LIBRARY_TAG_ITEM', "Error processing uploaded data (Report ID: {$id})");
             break;
         case Z_ERROR_SHARD_READ_ONLY:
         case Z_ERROR_SHARD_UNAVAILABLE:
             $this->error(503, 'SERVER_ERROR', Z_CONFIG::$MAINTENANCE_MESSAGE);
             break;
     }
     if (strpos($msg, "Lock wait timeout exceeded; try restarting transaction") !== false || strpos($msg, "MySQL error: Deadlock found when trying to get lock; try restarting transaction") !== false) {
         $this->error(500, 'TIMEOUT', "Sync upload timed out. Please try again in a few minutes. (Report ID: {$id})");
     }
     if (strpos($msg, "Data too long for column 'xmldata'") !== false) {
         $this->error(400, 'DATABASE_TOO_LARGE', "Databases of this size cannot yet be synced. Please check back soon. (Report ID: {$id})");
     }
     // On certain messages, send 400 to prevent auto-retry
     if (strpos($msg, " too long") !== false || strpos($msg, "First and last name are empty") !== false) {
         $this->error(400, 'ERROR_PROCESSING_UPLOAD_DATA', $explicit ? $msg : "Error processing uploaded data (Report ID: {$id})");
     }
     if (preg_match("/Incorrect datetime value: '([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})' " . "for column 'date(Added|Modified)'/", $msg, $matches)) {
         if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) {
             require_once '../model/ToolkitVersionComparator.inc.php';
             if (ToolkitVersionComparator::compare($_SERVER['HTTP_X_ZOTERO_VERSION'], "2.1rc1") < 0) {
                 $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Upgrade to Zotero 2.1rc1 when available to fix automatically.";
             } else {
                 $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Sync again to correct automatically.";
             }
         } else {
             $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Upgrade to Zotero 2.1rc1 when available to fix automatically.";
         }
         $this->error(400, 'INVALID_TIMESTAMP', $msg);
     }
     $this->error(500, 'ERROR_PROCESSING_UPLOAD_DATA', $explicit ? $msg : "Error processing uploaded data (Report ID: {$id})");
 }
 private static function extractZip($file, $destDir)
 {
     $za = new ZipArchive();
     $za->open($file);
     $entries = array();
     for ($i = 0, $max = $za->numFiles; $i < $max; $i++) {
         $stat = $za->statIndex($i);
         // Skip files not at the top level
         if ($stat['name'] != basename($stat['name'])) {
             continue;
         }
         // Skip dot files or ztmp (which we use as temp dir)
         if ($stat['name'][0] == '.' || $stat['name'] == 'ztmp') {
             continue;
         }
         if (preg_match("/%ZB64\$/", $stat['name'])) {
             $filename = Z_Base64::decode(substr($stat['name'], 0, -5));
             $filename = self::decodeRelativeDescriptorString($filename);
             $za->renameIndex($i, $filename);
         } else {
             $filename = $stat['name'];
         }
         $entries[] = $filename;
     }
     $success = $za->extractTo($destDir, $entries);
     $za->close();
     if (!$success) {
         Z_Core::logError($za->getStatusString());
     }
     return $success;
 }
 private function handleUploadError(Exception $e, $xmldata)
 {
     $msg = $e->getMessage();
     if ($msg[0] == '=') {
         $msg = substr($msg, 1);
         $explicit = true;
         // TODO: more specific error messages
     } else {
         $explicit = false;
     }
     switch ($e->getCode()) {
         case Z_ERROR_TAG_TOO_LONG:
             break;
         default:
             Z_Core::logError($msg);
     }
     if (true || !$explicit) {
         if (Z_ENV_TESTING_SITE) {
             switch ($e->getCode()) {
                 case Z_ERROR_COLLECTION_NOT_FOUND:
                 case Z_ERROR_CREATOR_NOT_FOUND:
                 case Z_ERROR_ITEM_NOT_FOUND:
                 case Z_ERROR_TAG_TOO_LONG:
                 case Z_ERROR_LIBRARY_ACCESS_DENIED:
                 case Z_ERROR_TAG_LINKED_ITEM_NOT_FOUND:
                     break;
                 default:
                     throw $e;
             }
             $id = 'N/A';
         } else {
             $id = substr(md5(uniqid(rand(), true)), 0, 8);
             $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 .= $e;
             switch ($e->getCode()) {
                 // Don't log uploaded data for some errors
                 case Z_ERROR_TAG_TOO_LONG:
                     break;
                 default:
                     $str .= "\n\n" . $xmldata;
             }
             file_put_contents(Z_CONFIG::$SYNC_ERROR_PATH . $id, $str);
         }
     }
     Zotero_DB::rollback(true);
     switch ($e->getCode()) {
         case Z_ERROR_LIBRARY_ACCESS_DENIED:
             preg_match('/[Ll]ibrary ([0-9]+)/', $e->getMessage(), $matches);
             $libraryID = $matches ? $matches[1] : null;
             $this->error(400, 'LIBRARY_ACCESS_DENIED', "Cannot make changes to library (Report ID: {$id})", array('libraryID' => $libraryID));
             break;
         case Z_ERROR_ITEM_NOT_FOUND:
         case Z_ERROR_COLLECTION_NOT_FOUND:
         case Z_ERROR_CREATOR_NOT_FOUND:
             $this->error(500, "FULL_SYNC_REQUIRED", "Please perform a full sync in the Sync->Reset pane of the Zotero preferences. (Report ID: {$id})");
             break;
         case Z_ERROR_TAG_TOO_LONG:
             $message = $e->getMessage();
             preg_match("/Tag '(.+)' too long/s", $message, $matches);
             if ($matches) {
                 $name = $matches[1];
                 $this->error(400, "TAG_TOO_LONG", "Tag '" . mb_substr($name, 0, 50) . "…' too long", array(), array("tag" => $name));
             }
             break;
         case Z_ERROR_COLLECTION_TOO_LONG:
             $message = $e->getMessage();
             preg_match("/Collection '(.+)' too long/s", $message, $matches);
             if ($matches) {
                 $name = $matches[1];
                 $this->error(400, "COLLECTION_TOO_LONG", "Collection '" . mb_substr($name, 0, 50) . "…' too long", array(), array("collection" => $name));
             }
             break;
         case Z_ERROR_ARRAY_SIZE_MISMATCH:
             $this->error(400, 'DATABASE_TOO_LARGE', "Databases of this size cannot yet be synced. Please check back soon. (Report ID: {$id})");
             break;
         case Z_ERROR_TAG_LINKED_ITEM_NOT_FOUND:
             $this->error(400, 'WRONG_LIBRARY_TAG_ITEM', "Error processing uploaded data (Report ID: {$id})");
             break;
         case Z_ERROR_SHARD_READ_ONLY:
         case Z_ERROR_SHARD_UNAVAILABLE:
             $this->error(503, 'SERVER_ERROR', Z_CONFIG::$MAINTENANCE_MESSAGE);
             break;
     }
     if (strpos($msg, "Lock wait timeout exceeded; try restarting transaction") !== false || strpos($msg, "MySQL error: Deadlock found when trying to get lock; try restarting transaction") !== false) {
         $this->error(500, 'TIMEOUT', "Sync upload timed out. Please try again in a few minutes. (Report ID: {$id})");
     }
     if (strpos($msg, "Data too long for column 'xmldata'") !== false) {
         $this->error(400, 'DATABASE_TOO_LARGE', "Databases of this size cannot yet be synced. Please check back soon. (Report ID: {$id})");
     }
     // On certain messages, send 400 to prevent auto-retry
     if (strpos($msg, " too long") !== false || strpos($msg, "First and last name are empty") !== false) {
         $this->error(400, 'ERROR_PROCESSING_UPLOAD_DATA', $explicit ? $msg : "Error processing uploaded data (Report ID: {$id})");
     }
     if (preg_match("/Incorrect datetime value: '([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})' " . "for column 'date(Added|Modified)'/", $msg, $matches)) {
         if (isset($_SERVER['HTTP_X_ZOTERO_VERSION'])) {
             require_once '../model/ToolkitVersionComparator.inc.php';
             if (ToolkitVersionComparator::compare($_SERVER['HTTP_X_ZOTERO_VERSION'], "2.1rc1") < 0) {
                 $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Upgrade to Zotero 2.1rc1 when available to fix automatically.";
             } else {
                 $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Sync again to correct automatically.";
             }
         } else {
             $msg = "Invalid timestamp '{$matches[1]}' in uploaded data. Upgrade to Zotero 2.1rc1 when available to fix automatically.";
         }
         $this->error(400, 'INVALID_TIMESTAMP', $msg);
     }
     $this->error(500, 'ERROR_PROCESSING_UPLOAD_DATA', $explicit ? $msg : "Error processing uploaded data (Report ID: {$id})");
 }