Ejemplo n.º 1
0
    public function testGetLongDataValueFromXML()
    {
        $longTag = "Longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong";
        $xml = <<<EOD
\t\t\t<data>
\t\t\t\t<creators><creator/></creators>
\t\t\t\t<tags>
\t\t\t\t\t<tag libraryID="1" key="AAAAAAAA" name="Test" dateAdded="2009-04-13 12:22:31" dateModified="2009-08-06 10:21:20">
\t\t\t\t\t\t<items>AAAAAAAA</items>
\t\t\t\t\t</tag>
\t\t\t\t\t<tag libraryID="1" key="BBBBBBBB" name="{$longTag}" dateAdded="2009-04-13 12:22:31" dateModified="2009-08-06 10:21:20"/>
\t\t\t\t</tags>
\t\t\t</data>
EOD;
        $doc = new DOMDocument();
        $doc->loadXML($xml);
        $tag = Zotero_Tags::getLongDataValueFromXML($doc);
        $this->assertEquals($longTag, $tag);
    }
Ejemplo n.º 2
0
 public function deleted()
 {
     if ($this->apiVersion < 2) {
         $this->e404();
     }
     $this->allowMethods(array('GET'));
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
     // TEMP: sync transition
     if ($this->queryParams['sincetime'] !== null) {
         $deleted = array("collections" => Zotero_Collections::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "items" => Zotero_Items::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "searches" => Zotero_Searches::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "tags" => Zotero_Tags::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true), "settings" => Zotero_Settings::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['sincetime'], true));
         echo Zotero_Utilities::formatJSON($deleted);
         $this->end();
     }
     if ($this->queryParams['since'] === null) {
         $this->e400("'since' parameter must be provided");
     }
     $deleted = array("collections" => Zotero_Collections::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "items" => Zotero_Items::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "searches" => Zotero_Searches::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "tags" => Zotero_Tags::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']), "settings" => Zotero_Settings::getDeleteLogKeys($this->objectLibraryID, $this->queryParams['since']));
     echo Zotero_Utilities::formatJSON($deleted);
     $this->end();
 }
Ejemplo n.º 3
0
 public static function search($libraryID, $onlyTopLevel = false, $params = array(), $includeTrashed = false, Zotero_Permissions $permissions = null)
 {
     $rnd = "_" . uniqid($libraryID . "_");
     $results = array('results' => array(), 'total' => 0);
     // Default empty library
     if ($libraryID === 0) {
         return $results;
     }
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $includeNotes = true;
     if ($permissions && !$permissions->canAccess($libraryID, 'notes')) {
         $includeNotes = false;
     }
     // Pass a list of itemIDs, for when the initial search is done via SQL
     $itemIDs = !empty($params['itemIDs']) ? $params['itemIDs'] : array();
     $itemKeys = $params['itemKey'];
     $titleSort = !empty($params['sort']) && $params['sort'] == 'title';
     $parentItemSort = !empty($params['sort']) && in_array($params['sort'], ['itemType', 'dateAdded', 'dateModified', 'serverDateModified', 'addedBy']);
     $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT ";
     // In /top mode, use the parent item's values for most joins
     if ($onlyTopLevel) {
         $itemIDSelector = "COALESCE(IA.sourceItemID, INo.sourceItemID, I.itemID)";
         $itemKeySelector = "COALESCE(IP.key, I.key)";
         $itemVersionSelector = "COALESCE(IP.version, I.version)";
         $itemTypeIDSelector = "COALESCE(IP.itemTypeID, I.itemTypeID)";
     } else {
         $itemIDSelector = "I.itemID";
         $itemKeySelector = "I.key";
         $itemVersionSelector = "I.version";
         $itemTypeIDSelector = "I.itemTypeID";
     }
     if ($params['format'] == 'keys' || $params['format'] == 'versions') {
         // In /top mode, display the parent item of matching items
         $sql .= "{$itemKeySelector} AS `key`";
         if ($params['format'] == 'versions') {
             $sql .= ", {$itemVersionSelector} AS version";
         }
     } else {
         $sql .= "{$itemIDSelector} AS itemID";
     }
     $sql .= " FROM items I ";
     $sqlParams = array($libraryID);
     // For /top, we need the parent itemID
     if ($onlyTopLevel) {
         $sql .= "LEFT JOIN itemAttachments IA ON (IA.itemID=I.itemID) ";
     }
     // For /top, we need the parent itemID; for 'q' we need the note; for sorting by title,
     // we need the note title
     if ($onlyTopLevel || !empty($params['q']) || $titleSort) {
         $sql .= "LEFT JOIN itemNotes INo ON (INo.itemID=I.itemID) ";
     }
     // For some /top requests, pull in the parent item's items row
     if ($onlyTopLevel && ($params['format'] == 'keys' || $params['format'] == 'versions' || $parentItemSort)) {
         $sql .= "LEFT JOIN items IP ON ({$itemIDSelector}=IP.itemID) ";
     }
     // Pull in titles
     if (!empty($params['q']) || $titleSort) {
         $titleFieldIDs = array_merge(array(Zotero_ItemFields::getID('title')), Zotero_ItemFields::getTypeFieldsFromBase('title'));
         $sql .= "LEFT JOIN itemData IDT ON (IDT.itemID=I.itemID AND IDT.fieldID IN " . "(" . implode(',', $titleFieldIDs) . ")) ";
     }
     // When sorting by title in /top mode, we need the title of the parent item
     if ($onlyTopLevel && $titleSort) {
         $titleSortDataTable = "IDTSort";
         $titleSortNoteTable = "INoSort";
         $sql .= "LEFT JOIN itemData IDTSort ON (IDTSort.itemID={$itemIDSelector} AND " . "IDTSort.fieldID IN (" . implode(',', $titleFieldIDs) . ")) " . "LEFT JOIN itemNotes INoSort ON (INoSort.itemID={$itemIDSelector}) ";
     } else {
         $titleSortDataTable = "IDT";
         $titleSortNoteTable = "INo";
     }
     if (!empty($params['q'])) {
         // Pull in creators
         $sql .= "LEFT JOIN itemCreators IC ON (IC.itemID=I.itemID) " . "LEFT JOIN creators C ON (C.creatorID=IC.creatorID) ";
         // Pull in dates
         $dateFieldIDs = array_merge(array(Zotero_ItemFields::getID('date')), Zotero_ItemFields::getTypeFieldsFromBase('date'));
         $sql .= "LEFT JOIN itemData IDD ON (IDD.itemID=I.itemID AND IDD.fieldID IN " . "(" . implode(',', $dateFieldIDs) . ")) ";
     }
     if ($includeTrashed) {
         if (!empty($params['trashedItemsOnly'])) {
             $sql .= "JOIN deletedItems DI ON (DI.itemID=I.itemID) ";
         }
     } else {
         $sql .= "LEFT JOIN deletedItems DI ON (DI.itemID=I.itemID) ";
         // In /top mode, we don't want to show results for deleted parents or children
         if ($onlyTopLevel) {
             $sql .= "LEFT JOIN deletedItems DIP ON (DIP.itemID={$itemIDSelector}) ";
         }
     }
     if (!empty($params['sort'])) {
         switch ($params['sort']) {
             case 'title':
             case 'creator':
                 $sql .= "LEFT JOIN itemSortFields ISF ON (ISF.itemID={$itemIDSelector}) ";
                 break;
             case 'date':
                 // When sorting by date in /top mode, we need the date of the parent item
                 if ($onlyTopLevel) {
                     $sortTable = "IDDSort";
                     // Pull in dates
                     $dateFieldIDs = array_merge(array(Zotero_ItemFields::getID('date')), Zotero_ItemFields::getTypeFieldsFromBase('date'));
                     $sql .= "LEFT JOIN itemData IDDSort ON (IDDSort.itemID={$itemIDSelector} AND " . "IDDSort.fieldID IN (" . implode(',', $dateFieldIDs) . ")) ";
                 } else {
                     $sortTable = "IDD";
                     if (empty($params['q'])) {
                         $dateFieldIDs = array_merge(array(Zotero_ItemFields::getID('date')), Zotero_ItemFields::getTypeFieldsFromBase('date'));
                         $sql .= "LEFT JOIN itemData IDD ON (IDD.itemID=I.itemID AND IDD.fieldID IN (" . implode(',', $dateFieldIDs) . ")) ";
                     }
                 }
                 break;
             case 'itemType':
                 $locale = 'en-US';
                 $types = Zotero_ItemTypes::getAll($locale);
                 // TEMP: get localized string
                 // DEBUG: Why is attachment skipped in getAll()?
                 $types[] = array('id' => 14, 'localized' => 'Attachment');
                 foreach ($types as $type) {
                     $sql2 = "INSERT IGNORE INTO tmpItemTypeNames VALUES (?, ?, ?)";
                     Zotero_DB::query($sql2, array($type['id'], $locale, $type['localized']), $shardID);
                 }
                 // Join temp table to query
                 $sql .= "JOIN tmpItemTypeNames TITN ON (TITN.itemTypeID={$itemTypeIDSelector}) ";
                 break;
             case 'addedBy':
                 $isGroup = Zotero_Libraries::getType($libraryID) == 'group';
                 if ($isGroup) {
                     $sql2 = "SELECT DISTINCT createdByUserID FROM items\n\t\t\t\t\t\t\t\tJOIN groupItems USING (itemID) WHERE\n\t\t\t\t\t\t\t\tcreatedByUserID IS NOT NULL AND ";
                     if ($itemIDs) {
                         $sql2 .= "itemID IN (" . implode(', ', array_fill(0, sizeOf($itemIDs), '?')) . ") ";
                         $createdByUserIDs = Zotero_DB::columnQuery($sql2, $itemIDs, $shardID);
                     } else {
                         $sql2 .= "libraryID=?";
                         $createdByUserIDs = Zotero_DB::columnQuery($sql2, $libraryID, $shardID);
                     }
                     // Populate temp table with usernames
                     if ($createdByUserIDs) {
                         $toAdd = array();
                         foreach ($createdByUserIDs as $createdByUserID) {
                             $toAdd[] = array($createdByUserID, Zotero_Users::getUsername($createdByUserID));
                         }
                         $sql2 = "INSERT IGNORE INTO tmpCreatedByUsers VALUES ";
                         Zotero_DB::bulkInsert($sql2, $toAdd, 50, false, $shardID);
                         // Join temp table to query
                         $sql .= "LEFT JOIN groupItems GI ON (GI.itemID=I.itemID)\n\t\t\t\t\t\t\t\t\tLEFT JOIN tmpCreatedByUsers TCBU ON (TCBU.userID=GI.createdByUserID) ";
                     }
                 }
                 break;
         }
     }
     $sql .= "WHERE I.libraryID=? ";
     if (!$includeTrashed) {
         $sql .= "AND DI.itemID IS NULL ";
         // Hide deleted parents in /top mode
         if ($onlyTopLevel) {
             $sql .= "AND DIP.itemID IS NULL ";
         }
     }
     // Search on title, creators, and dates
     if (!empty($params['q'])) {
         $sql .= "AND (";
         $sql .= "IDT.value LIKE ? ";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= "OR INo.title LIKE ? ";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= "OR TRIM(CONCAT(firstName, ' ', lastName)) LIKE ? ";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= "OR SUBSTR(IDD.value, 1, 4) = ?";
         $sqlParams[] = $params['q'];
         // Full-text search
         if ($params['qmode'] == 'everything') {
             $ftKeys = Zotero_FullText::searchInLibrary($libraryID, $params['q']);
             if ($ftKeys) {
                 $sql .= " OR I.key IN (" . implode(', ', array_fill(0, sizeOf($ftKeys), '?')) . ") ";
                 $sqlParams = array_merge($sqlParams, $ftKeys);
             }
         }
         $sql .= ") ";
     }
     // Search on itemType
     if (!empty($params['itemType'])) {
         $itemTypes = Zotero_API::getSearchParamValues($params, 'itemType');
         if ($itemTypes) {
             if (sizeOf($itemTypes) > 1) {
                 throw new Exception("Cannot specify 'itemType' more than once", Z_ERROR_INVALID_INPUT);
             }
             $itemTypes = $itemTypes[0];
             $itemTypeIDs = array();
             foreach ($itemTypes['values'] as $itemType) {
                 $itemTypeID = Zotero_ItemTypes::getID($itemType);
                 if (!$itemTypeID) {
                     throw new Exception("Invalid itemType '{$itemType}'", Z_ERROR_INVALID_INPUT);
                 }
                 $itemTypeIDs[] = $itemTypeID;
             }
             $sql .= "AND I.itemTypeID " . ($itemTypes['negation'] ? "NOT " : "") . "IN (" . implode(',', array_fill(0, sizeOf($itemTypeIDs), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $itemTypeIDs);
         }
     }
     if (!$includeNotes) {
         $sql .= "AND I.itemTypeID != 1 ";
     }
     if (!empty($params['since'])) {
         $sql .= "AND {$itemVersionSelector} > ? ";
         $sqlParams[] = $params['since'];
     }
     // TEMP: for sync transition
     if (!empty($params['sincetime']) && $params['sincetime'] != 1) {
         $sql .= "AND I.serverDateModified >= FROM_UNIXTIME(?) ";
         $sqlParams[] = $params['sincetime'];
     }
     // Tags
     //
     // ?tag=foo
     // ?tag=foo bar // phrase
     // ?tag=-foo // negation
     // ?tag=\-foo // literal hyphen (only for first character)
     // ?tag=foo&tag=bar // AND
     $tagSets = Zotero_API::getSearchParamValues($params, 'tag');
     if ($tagSets) {
         $sql2 = "SELECT itemID FROM items WHERE libraryID=?\n";
         $sqlParams2 = array($libraryID);
         $positives = array();
         $negatives = array();
         foreach ($tagSets as $set) {
             $tagIDs = array();
             foreach ($set['values'] as $tag) {
                 $ids = Zotero_Tags::getIDs($libraryID, $tag, true);
                 if (!$ids) {
                     $ids = array(0);
                 }
                 $tagIDs = array_merge($tagIDs, $ids);
             }
             $tagIDs = array_unique($tagIDs);
             $tmpSQL = "SELECT itemID FROM items JOIN itemTags USING (itemID) " . "WHERE tagID IN (" . implode(',', array_fill(0, sizeOf($tagIDs), '?')) . ")";
             $ids = Zotero_DB::columnQuery($tmpSQL, $tagIDs, $shardID);
             if (!$ids) {
                 // If no negative tags, skip this tag set
                 if ($set['negation']) {
                     continue;
                 }
                 // If no positive tags, return no matches
                 return $results;
             }
             $ids = $ids ? $ids : array();
             $sql2 .= " AND itemID " . ($set['negation'] ? "NOT " : "") . " IN (" . implode(',', array_fill(0, sizeOf($ids), '?')) . ")";
             $sqlParams2 = array_merge($sqlParams2, $ids);
         }
         $tagItems = Zotero_DB::columnQuery($sql2, $sqlParams2, $shardID);
         // No matches
         if (!$tagItems) {
             return $results;
         }
         // Combine with passed ids
         if ($itemIDs) {
             $itemIDs = array_intersect($itemIDs, $tagItems);
             // None of the tag matches match the passed ids
             if (!$itemIDs) {
                 return $results;
             }
         } else {
             $itemIDs = $tagItems;
         }
     }
     if ($itemIDs) {
         $sql .= "AND I.itemID IN (" . implode(', ', array_fill(0, sizeOf($itemIDs), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $itemIDs);
     }
     if ($itemKeys) {
         $sql .= "AND I.key IN (" . implode(', ', array_fill(0, sizeOf($itemKeys), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $itemKeys);
     }
     $sql .= "ORDER BY ";
     if (!empty($params['sort'])) {
         switch ($params['sort']) {
             case 'dateAdded':
             case 'dateModified':
             case 'serverDateModified':
                 if ($onlyTopLevel) {
                     $orderSQL = "IP." . $params['sort'];
                 } else {
                     $orderSQL = "I." . $params['sort'];
                 }
                 break;
             case 'itemType':
                 $orderSQL = "TITN.itemTypeName";
                 /*
                 // Optional method for sorting by localized item type name, which would avoid
                 // the INSERT and JOIN above and allow these requests to use DB read replicas
                 $locale = 'en-US';
                 $types = Zotero_ItemTypes::getAll($locale);
                 // TEMP: get localized string
                 // DEBUG: Why is attachment skipped in getAll()?
                 $types[] = [
                 	'id' => 14,
                 	'localized' => 'Attachment'
                 ];
                 usort($types, function ($a, $b) {
                 	return strcasecmp($a['localized'], $b['localized']);
                 });
                 // Pass order of localized item type names for sorting
                 // e.g., FIELD(14, 12, 14, 26...) for sorting "Attachment" after "Artwork"
                 $orderSQL = "FIELD($itemTypeIDSelector, "
                 	. implode(", ", array_map(function ($x) {
                 		return $x['id'];
                 	}, $types)) . ")";
                 // If itemTypeID isn't found in passed list (currently only for NSF Reviewer),
                 // sort last
                 $orderSQL = "IFNULL(NULLIF($orderSQL, 0), 99999)";
                 // All items have types, so no need to check for empty sort values
                 $params['emptyFirst'] = true;
                 */
                 break;
             case 'title':
                 $orderSQL = "IFNULL(COALESCE(sortTitle, {$titleSortDataTable}.value, {$titleSortNoteTable}.title), '')";
                 break;
             case 'creator':
                 $orderSQL = "ISF.creatorSummary";
                 break;
                 // TODO: generic base field mapping-aware sorting
             // TODO: generic base field mapping-aware sorting
             case 'date':
                 $orderSQL = "{$sortTable}.value";
                 break;
             case 'addedBy':
                 if ($isGroup && $createdByUserIDs) {
                     $orderSQL = "TCBU.username";
                 } else {
                     $orderSQL = ($onlyTopLevel ? "IP" : "I") . ".dateAdded";
                 }
                 break;
             case 'itemKeyList':
                 $orderSQL = "FIELD(I.key," . implode(',', array_fill(0, sizeOf($itemKeys), '?')) . ")";
                 $sqlParams = array_merge($sqlParams, $itemKeys);
                 break;
             default:
                 $fieldID = Zotero_ItemFields::getID($params['sort']);
                 if (!$fieldID) {
                     throw new Exception("Invalid order field '" . $params['sort'] . "'");
                 }
                 $orderSQL = "(SELECT value FROM itemData WHERE itemID=I.itemID AND fieldID=?)";
                 if (!$params['emptyFirst']) {
                     $sqlParams[] = $fieldID;
                 }
                 $sqlParams[] = $fieldID;
         }
         if (!empty($params['direction'])) {
             $dir = $params['direction'];
         } else {
             $dir = "ASC";
         }
         if (!$params['emptyFirst']) {
             $sql .= "IFNULL({$orderSQL}, '') = '' {$dir}, ";
         }
         $sql .= $orderSQL . " {$dir}, ";
     }
     $sql .= "I.version " . (!empty($params['direction']) ? $params['direction'] : "ASC") . ", I.itemID " . (!empty($params['direction']) ? $params['direction'] : "ASC") . " ";
     if (!empty($params['limit'])) {
         $sql .= "LIMIT ?, ?";
         $sqlParams[] = $params['start'] ? $params['start'] : 0;
         $sqlParams[] = $params['limit'];
     }
     // Log SQL statement with embedded parameters
     /*if (true || !empty($_GET['sqldebug'])) {
     			error_log($onlyTopLevel);
     			
     			$debugSQL = "";
     			$parts = explode("?", $sql);
     			$debugSQLParams = $sqlParams;
     			foreach ($parts as $part) {
     				$val = array_shift($debugSQLParams);
     				$debugSQL .= $part;
     				if (!is_null($val)) {
     					$debugSQL .= is_int($val) ? $val : '"' . $val . '"';
     				}
     			}
     			error_log($debugSQL . ";");
     		}*/
     if ($params['format'] == 'versions') {
         $rows = Zotero_DB::query($sql, $sqlParams, $shardID);
     } else {
         $rows = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
     }
     $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
     if ($rows) {
         if ($params['format'] == 'keys') {
             $results['results'] = $rows;
         } else {
             if ($params['format'] == 'versions') {
                 foreach ($rows as $row) {
                     $results['results'][$row['key']] = $row['version'];
                 }
             } else {
                 $results['results'] = Zotero_Items::get($libraryID, $rows);
             }
         }
     }
     return $results;
 }
Ejemplo n.º 4
0
 public static function search($libraryID, $params)
 {
     $results = array('results' => array(), 'total' => 0);
     // Default empty library
     if ($libraryID === 0) {
         return $results;
     }
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT tagID FROM tags " . "JOIN itemTags USING (tagID) WHERE libraryID=? ";
     $sqlParams = array($libraryID);
     // Pass a list of tagIDs, for when the initial search is done via SQL
     $tagIDs = !empty($params['tagIDs']) ? $params['tagIDs'] : array();
     // Filter for specific tags with "?tag=foo || bar"
     $tagNames = !empty($params['tag']) ? explode(' || ', $params['tag']) : array();
     if ($tagIDs) {
         $sql .= "AND tagID IN (" . implode(', ', array_fill(0, sizeOf($tagIDs), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $tagIDs);
     }
     if ($tagNames) {
         $sql .= "AND `name` IN (" . implode(', ', array_fill(0, sizeOf($tagNames), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $tagNames);
     }
     if (!empty($params['q'])) {
         if (!is_array($params['q'])) {
             $params['q'] = array($params['q']);
         }
         foreach ($params['q'] as $q) {
             $sql .= "AND name LIKE ? ";
             $sqlParams[] = "%{$q}%";
         }
     }
     $tagTypeSets = Zotero_API::getSearchParamValues($params, 'tagType');
     if ($tagTypeSets) {
         $positives = array();
         $negatives = array();
         foreach ($tagTypeSets as $set) {
             if ($set['negation']) {
                 $negatives = array_merge($negatives, $set['values']);
             } else {
                 $positives = array_merge($positives, $set['values']);
             }
         }
         if ($positives) {
             $sql .= "AND type IN (" . implode(',', array_fill(0, sizeOf($positives), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $positives);
         }
         if ($negatives) {
             $sql .= "AND type NOT IN (" . implode(',', array_fill(0, sizeOf($negatives), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $negatives);
         }
     }
     if (!empty($params['since'])) {
         $sql .= "AND version > ? ";
         $sqlParams[] = $params['since'];
     }
     if (!empty($params['sort'])) {
         $order = $params['sort'];
         if ($order == 'title') {
             // Force a case-insensitive sort
             $sql .= "ORDER BY name COLLATE utf8_unicode_ci ";
         } else {
             if ($order == 'numItems') {
                 $sql .= "GROUP BY tags.tagID ORDER BY COUNT(tags.tagID)";
             } else {
                 $sql .= "ORDER BY {$order} ";
             }
         }
         if (!empty($params['direction'])) {
             $sql .= " " . $params['direction'] . " ";
         }
     }
     if (!empty($params['limit'])) {
         $sql .= "LIMIT ?, ?";
         $sqlParams[] = $params['start'] ? $params['start'] : 0;
         $sqlParams[] = $params['limit'];
     }
     $ids = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
     $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
     if ($ids) {
         $tags = array();
         foreach ($ids as $id) {
             $tags[] = Zotero_Tags::get($libraryID, $id);
         }
         $results['results'] = $tags;
     }
     return $results;
 }
Ejemplo n.º 5
0
	protected function loadTags($reload = false) {
		if ($this->loaded['tags'] && !$reload) return;
		
		if (!$this->id) {
			return;
		}
		
		Z_Core::debug("Loading tags for item $this->id");
		
		$sql = "SELECT tagID FROM itemTags JOIN tags USING (tagID) WHERE itemID=?";
		$tagIDs = Zotero_DB::columnQuery(
			$sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID)
		);
		$this->tags = [];
		if ($tagIDs) {
			foreach ($tagIDs as $tagID) {
				$this->tags[] = Zotero_Tags::get($this->libraryID, $tagID, true);
			}
		}
		$this->loaded['tags'] = true;
		$this->clearChanged('tags');
	}
Ejemplo n.º 6
0
 /**
  * $tags is an array of objects with properties 'tag' and 'type'
  */
 public function setTags($newTags)
 {
     if (!$this->id) {
         throw new Exception('itemID not set');
     }
     $numTags = $this->numTags();
     if (!$newTags && !$numTags) {
         return false;
     }
     Zotero_DB::beginTransaction();
     $existingTags = $this->getTags();
     $toAdd = array();
     $toRemove = array();
     // Get new tags not in existing
     for ($i = 0, $len = sizeOf($newTags); $i < $len; $i++) {
         if (!isset($newTags[$i]->type)) {
             $newTags[$i]->type = 0;
         }
         $name = trim($newTags[$i]->tag);
         // 'tag', not 'name', since that's what JSON uses
         $type = $newTags[$i]->type;
         foreach ($existingTags as $tag) {
             // Do a case-insensitive comparison, to match the client
             if (strtolower($tag->name) == strtolower($name) && $tag->type == $type) {
                 continue 2;
             }
         }
         $toAdd[] = $newTags[$i];
     }
     // Get existing tags not in new
     for ($i = 0, $len = sizeOf($existingTags); $i < $len; $i++) {
         $name = $existingTags[$i]->name;
         $type = $existingTags[$i]->type;
         foreach ($newTags as $tag) {
             if (strtolower($tag->tag) == strtolower($name) && $tag->type == $type) {
                 continue 2;
             }
         }
         $toRemove[] = $existingTags[$i];
     }
     foreach ($toAdd as $tag) {
         $name = $tag->tag;
         $type = $tag->type;
         $tagID = Zotero_Tags::getID($this->libraryID, $name, $type, true);
         if (!$tagID) {
             $tag = new Zotero_Tag();
             $tag->libraryID = $this->libraryID;
             $tag->name = $name;
             $tag->type = $type;
             $tagID = $tag->save();
         }
         $tag = Zotero_Tags::get($this->libraryID, $tagID);
         $tag->addItem($this->id);
         $tag->save();
     }
     foreach ($toRemove as $tag) {
         $tag->removeItem($this->id);
         $tag->save();
     }
     Zotero_DB::commit();
     return $toAdd || $toRemove;
 }
Ejemplo n.º 7
0
 private function load()
 {
     $libraryID = $this->libraryID;
     $id = $this->id;
     $key = $this->key;
     if (!$libraryID) {
         throw new Exception("Library ID not set");
     }
     if (!$id && !$key) {
         throw new Exception("ID or key not set");
     }
     // Cache tag data for the entire library
     if (true) {
         if ($id) {
             Z_Core::debug("Loading data for tag {$this->libraryID}/{$this->id}");
             $row = Zotero_Tags::getPrimaryDataByID($libraryID, $id);
         } else {
             Z_Core::debug("Loading data for tag {$this->libraryID}/{$this->key}");
             $row = Zotero_Tags::getPrimaryDataByKey($libraryID, $key);
         }
         $this->loaded = true;
         if (!$row) {
             return;
         }
         if ($row['libraryID'] != $libraryID) {
             throw new Exception("libraryID {$row['libraryID']} != {$this->libraryID}");
         }
         foreach ($row as $key => $val) {
             $this->{$key} = $val;
         }
     } else {
         // Use cached check for existence if possible
         if ($libraryID && $key) {
             if (!Zotero_Tags::existsByLibraryAndKey($libraryID, $key)) {
                 $this->loaded = true;
                 return;
             }
         }
         $shardID = Zotero_Shards::getByLibraryID($libraryID);
         $sql = Zotero_Tags::getPrimaryDataSQL();
         if ($id) {
             $sql .= "tagID=?";
             $stmt = Zotero_DB::getStatement($sql, false, $shardID);
             $data = Zotero_DB::rowQueryFromStatement($stmt, $id);
         } else {
             $sql .= "libraryID=? AND `key`=?";
             $stmt = Zotero_DB::getStatement($sql, false, $shardID);
             $data = Zotero_DB::rowQueryFromStatement($stmt, array($libraryID, $key));
         }
         $this->loaded = true;
         if (!$data) {
             return;
         }
         if ($data['libraryID'] != $libraryID) {
             throw new Exception("libraryID {$data['libraryID']} != {$libraryID}");
         }
         foreach ($data as $k => $v) {
             $this->{$k} = $v;
         }
     }
 }
Ejemplo n.º 8
0
    public function tags()
    {
        $this->allowMethods(array('GET'));
        if (!$this->permissions->canAccess($this->objectLibraryID)) {
            $this->e403();
        }
        $tags = array();
        $totalResults = 0;
        $name = $this->objectName;
        $fixedValues = array();
        // Set of tags matching name
        if ($name && $this->subset != 'tags') {
            $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $name);
            if (!$tagIDs) {
                $this->e404();
            }
            $title = "Tags matching ‘" . $name . "’";
        } else {
            if ($this->scopeObject) {
                // If id, redirect to key URL
                if ($this->scopeObjectID) {
                    if (!in_array($this->scopeObject, array("collections", "items"))) {
                        $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 . "/tags" . $qs);
                    exit;
                }
                switch ($this->scopeObject) {
                    case 'collections':
                        $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                        if (!$collection) {
                            $this->e404();
                        }
                        $title = "Tags in Collection ‘" . $collection->name . "’";
                        $counts = $collection->getTagItemCounts();
                        $tagIDs = array();
                        if ($counts) {
                            foreach ($counts as $tagID => $count) {
                                $tagIDs[] = $tagID;
                                $fixedValues[$tagID] = array('numItems' => $count);
                            }
                        }
                        break;
                    case 'items':
                        $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                        if (!$item) {
                            $this->e404();
                        }
                        $title = "Tags of '" . $item->getDisplayTitle() . "'";
                        $tagIDs = $item->getTags(true);
                        break;
                    default:
                        throw new Exception("Invalid tags scope object '{$this->scopeObject}'");
                }
            } else {
                $title = "Tags";
                $results = Zotero_Tags::getAllAdvanced($this->objectLibraryID, $this->queryParams);
                $tags = $results['objects'];
                $totalResults = $results['total'];
            }
        }
        if (!empty($tagIDs)) {
            foreach ($tagIDs as $tagID) {
                $tags[] = Zotero_Tags::get($this->objectLibraryID, $tagID);
            }
            // Fake sorting and limiting
            $totalResults = sizeOf($tags);
            $key = $this->queryParams['order'];
            // 'title' order means 'name' for tags
            if ($key == 'title') {
                $key = 'name';
            }
            $dir = $this->queryParams['sort'];
            $cmp = create_function('$a, $b', '$dir = "' . $dir . '" == "asc" ? 1 : -1;
				if ($a->' . $key . ' == $b->' . $key . ') {
					return 0;
				}
				else {
					return ($a->' . $key . ' > $b->' . $key . ') ? $dir : ($dir * -1);}');
            usort($tags, $cmp);
            $tags = array_slice($tags, $this->queryParams['start'], $this->queryParams['limit']);
        }
        $this->responseXML = Zotero_Atom::createAtomFeed($this->getFeedNamePrefix($this->objectLibraryID) . $title, $this->uri, $tags, $totalResults, $this->queryParams, $this->apiVersion, $this->permissions, $fixedValues);
        $this->end();
    }
Ejemplo n.º 9
0
 private static function processUploadInternal($userID, SimpleXMLElement $xml, $syncQueueID = null, $syncProcessID = null)
 {
     $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
     $affectedLibraries = self::parseAffectedLibraries($xml->asXML());
     // Relations-only uploads don't have affected libraries
     if (!$affectedLibraries) {
         $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($userID));
     }
     $processID = self::addUploadProcess($userID, $affectedLibraries, $syncQueueID, $syncProcessID);
     set_time_limit(5400);
     $profile = false;
     if ($profile) {
         $shardID = Zotero_Shards::getByUserID($userID);
         Zotero_DB::profileStart($shardID);
     }
     try {
         Zotero_DB::beginTransaction();
         // Mark libraries as updated
         foreach ($affectedLibraries as $libraryID) {
             Zotero_Libraries::updateVersion($libraryID);
         }
         $timestamp = Zotero_Libraries::updateTimestamps($affectedLibraries);
         Zotero_DB::registerTransactionTimestamp($timestamp);
         // Make sure no other upload sessions use this same timestamp
         // for any of these libraries, since we return >= 1 as the next
         // last sync time
         if (!Zotero_Libraries::setTimestampLock($affectedLibraries, $timestamp)) {
             throw new Exception("Library timestamp already used", Z_ERROR_LIBRARY_TIMESTAMP_ALREADY_USED);
         }
         $modifiedItems = array();
         // Add/update creators
         if ($xml->creators) {
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->creators);
             $xmlElements = $xmlElements->getElementsByTagName('creator');
             Zotero_DB::query("SET foreign_key_checks = 0");
             try {
                 $addedLibraryIDs = array();
                 $addedCreatorDataHashes = array();
                 foreach ($xmlElements as $xmlElement) {
                     $key = $xmlElement->getAttribute('key');
                     if (isset($keys[$key])) {
                         throw new Exception("Creator {$key} already processed");
                     }
                     $keys[$key] = true;
                     $creatorObj = Zotero_Creators::convertXMLToCreator($xmlElement);
                     if (Zotero_Utilities::unicodeTrim($creatorObj->firstName) === '' && Zotero_Utilities::unicodeTrim($creatorObj->lastName) === '') {
                         continue;
                     }
                     $addedLibraryIDs[] = $creatorObj->libraryID;
                     $changed = $creatorObj->save($userID);
                     // If the creator changed, we need to update all linked items
                     if ($changed) {
                         $modifiedItems = array_merge($modifiedItems, $creatorObj->getLinkedItems());
                     }
                 }
             } catch (Exception $e) {
                 Zotero_DB::query("SET foreign_key_checks = 1");
                 throw $e;
             }
             Zotero_DB::query("SET foreign_key_checks = 1");
             unset($keys);
             unset($xml->creators);
             //
             // Manual foreign key checks
             //
             // libraryID
             foreach (array_unique($addedLibraryIDs) as $addedLibraryID) {
                 $shardID = Zotero_Shards::getByLibraryID($addedLibraryID);
                 $sql = "SELECT COUNT(*) FROM shardLibraries WHERE libraryID=?";
                 if (!Zotero_DB::valueQuery($sql, $addedLibraryID, $shardID)) {
                     throw new Exception("libraryID inserted into `creators` not found in `shardLibraries` ({$addedLibraryID}, {$shardID})");
                 }
             }
         }
         // Add/update items
         $savedItems = array();
         if ($xml->items) {
             $childItems = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->items);
             $xmlElements = $xmlElements->getElementsByTagName('item');
             foreach ($xmlElements as $xmlElement) {
                 $libraryID = (int) $xmlElement->getAttribute('libraryID');
                 $key = $xmlElement->getAttribute('key');
                 if (isset($savedItems[$libraryID . "/" . $key])) {
                     throw new Exception("Item {$libraryID}/{$key} already processed");
                 }
                 $itemObj = Zotero_Items::convertXMLToItem($xmlElement);
                 if (!$itemObj->getSourceKey()) {
                     try {
                         $modified = $itemObj->save($userID);
                         if ($modified) {
                             $savedItems[$libraryID . "/" . $key] = true;
                         }
                     } catch (Exception $e) {
                         if (strpos($e->getMessage(), 'libraryIDs_do_not_match') !== false) {
                             throw new Exception($e->getMessage() . " ({$key})");
                         }
                         throw $e;
                     }
                 } else {
                     $childItems[] = $itemObj;
                 }
             }
             unset($xml->items);
             while ($childItem = array_shift($childItems)) {
                 $libraryID = $childItem->libraryID;
                 $key = $childItem->key;
                 if (isset($savedItems[$libraryID . "/" . $key])) {
                     throw new Exception("Item {$libraryID}/{$key} already processed");
                 }
                 $modified = $childItem->save($userID);
                 if ($modified) {
                     $savedItems[$libraryID . "/" . $key] = true;
                 }
             }
         }
         // Add/update collections
         if ($xml->collections) {
             $collections = array();
             $collectionSets = array();
             // DOM
             // Build an array of unsaved collection objects and the keys of child items
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->collections);
             $xmlElements = $xmlElements->getElementsByTagName('collection');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Collection {$key} already processed");
                 }
                 $keys[$key] = true;
                 $collectionObj = Zotero_Collections::convertXMLToCollection($xmlElement);
                 $xmlItems = $xmlElement->getElementsByTagName('items')->item(0);
                 // Fix an error if there's leading or trailing whitespace,
                 // which was possible in 2.0.3
                 if ($xmlItems) {
                     $xmlItems = trim($xmlItems->nodeValue);
                 }
                 $arr = array('obj' => $collectionObj, 'items' => $xmlItems ? explode(' ', $xmlItems) : array());
                 $collections[] = $collectionObj;
                 $collectionSets[] = $arr;
             }
             unset($keys);
             unset($xml->collections);
             self::saveCollections($collections, $userID);
             unset($collections);
             // Set child items
             foreach ($collectionSets as $collection) {
                 // Child items
                 if (isset($collection['items'])) {
                     $ids = array();
                     foreach ($collection['items'] as $key) {
                         $item = Zotero_Items::getByLibraryAndKey($collection['obj']->libraryID, $key);
                         if (!$item) {
                             throw new Exception("Child item '{$key}' of collection {$collection['obj']->id} not found", Z_ERROR_ITEM_NOT_FOUND);
                         }
                         $ids[] = $item->id;
                     }
                     $collection['obj']->setItems($ids);
                 }
             }
             unset($collectionSets);
         }
         // Add/update saved searches
         if ($xml->searches) {
             $searches = array();
             $keys = array();
             foreach ($xml->searches->search as $xmlElement) {
                 $key = (string) $xmlElement['key'];
                 if (isset($keys[$key])) {
                     throw new Exception("Search {$key} already processed");
                 }
                 $keys[$key] = true;
                 $searchObj = Zotero_Searches::convertXMLToSearch($xmlElement);
                 $searchObj->save($userID);
             }
             unset($xml->searches);
         }
         // Add/update tags
         if ($xml->tags) {
             $keys = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->tags);
             $xmlElements = $xmlElements->getElementsByTagName('tag');
             foreach ($xmlElements as $xmlElement) {
                 // TEMP
                 $tagItems = $xmlElement->getElementsByTagName('items');
                 if ($tagItems->length && $tagItems->item(0)->nodeValue == "") {
                     error_log("Skipping tag with no linked items");
                     continue;
                 }
                 $libraryID = (int) $xmlElement->getAttribute('libraryID');
                 $key = $xmlElement->getAttribute('key');
                 $lk = $libraryID . "/" . $key;
                 if (isset($keys[$lk])) {
                     throw new Exception("Tag {$lk} already processed");
                 }
                 $keys[$lk] = true;
                 $itemKeysToUpdate = array();
                 $tagObj = Zotero_Tags::convertXMLToTag($xmlElement, $itemKeysToUpdate);
                 // We need to update removed items, added items, and,
                 // if the tag itself has changed, existing items
                 $modifiedItems = array_merge($modifiedItems, array_map(function ($key) use($libraryID) {
                     return $libraryID . "/" . $key;
                 }, $itemKeysToUpdate));
                 $tagObj->save($userID, true);
             }
             unset($keys);
             unset($xml->tags);
         }
         // Add/update relations
         if ($xml->relations) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->relations);
             $xmlElements = $xmlElements->getElementsByTagName('relation');
             foreach ($xmlElements as $xmlElement) {
                 $relationObj = Zotero_Relations::convertXMLToRelation($xmlElement, $userLibraryID);
                 if ($relationObj->exists()) {
                     continue;
                 }
                 $relationObj->save($userID);
             }
             unset($keys);
             unset($xml->relations);
         }
         // Add/update settings
         if ($xml->settings) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->settings);
             $xmlElements = $xmlElements->getElementsByTagName('setting');
             foreach ($xmlElements as $xmlElement) {
                 $settingObj = Zotero_Settings::convertXMLToSetting($xmlElement);
                 $settingObj->save($userID);
             }
             unset($xml->settings);
         }
         if ($xml->fulltexts) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->fulltexts);
             $xmlElements = $xmlElements->getElementsByTagName('fulltext');
             foreach ($xmlElements as $xmlElement) {
                 Zotero_FullText::indexFromXML($xmlElement, $userID);
             }
             unset($xml->fulltexts);
         }
         // TODO: loop
         if ($xml->deleted) {
             // Delete collections
             if ($xml->deleted->collections) {
                 Zotero_Collections::deleteFromXML($xml->deleted->collections, $userID);
             }
             // Delete items
             if ($xml->deleted->items) {
                 Zotero_Items::deleteFromXML($xml->deleted->items, $userID);
             }
             // Delete creators
             if ($xml->deleted->creators) {
                 Zotero_Creators::deleteFromXML($xml->deleted->creators, $userID);
             }
             // Delete saved searches
             if ($xml->deleted->searches) {
                 Zotero_Searches::deleteFromXML($xml->deleted->searches, $userID);
             }
             // Delete tags
             if ($xml->deleted->tags) {
                 $xmlElements = dom_import_simplexml($xml->deleted->tags);
                 $xmlElements = $xmlElements->getElementsByTagName('tag');
                 foreach ($xmlElements as $xmlElement) {
                     $libraryID = (int) $xmlElement->getAttribute('libraryID');
                     $key = $xmlElement->getAttribute('key');
                     $tagObj = Zotero_Tags::getByLibraryAndKey($libraryID, $key);
                     if (!$tagObj) {
                         continue;
                     }
                     // We need to update all items on the deleted tag
                     $modifiedItems = array_merge($modifiedItems, array_map(function ($key) use($libraryID) {
                         return $libraryID . "/" . $key;
                     }, $tagObj->getLinkedItems(true)));
                 }
                 Zotero_Tags::deleteFromXML($xml->deleted->tags, $userID);
             }
             // Delete relations
             if ($xml->deleted->relations) {
                 Zotero_Relations::deleteFromXML($xml->deleted->relations, $userID);
             }
             // Delete relations
             if ($xml->deleted->settings) {
                 Zotero_Settings::deleteFromXML($xml->deleted->settings, $userID);
             }
         }
         $toUpdate = array();
         foreach ($modifiedItems as $item) {
             // libraryID/key string
             if (is_string($item)) {
                 if (isset($savedItems[$item])) {
                     continue;
                 }
                 $savedItems[$item] = true;
                 list($libraryID, $key) = explode("/", $item);
                 $item = Zotero_Items::getByLibraryAndKey($libraryID, $key);
                 if (!$item) {
                     // Item was deleted
                     continue;
                 }
             } else {
                 $lk = $item->libraryID . "/" . $item->key;
                 if (isset($savedItems[$lk])) {
                     continue;
                 }
                 $savedItems[$lk] = true;
             }
             $toUpdate[] = $item;
         }
         Zotero_Items::updateVersions($toUpdate, $userID);
         unset($savedItems);
         unset($modifiedItems);
         try {
             self::removeUploadProcess($processID);
         } catch (Exception $e) {
             if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
                 // Reconnect
                 error_log("Reconnecting to MySQL master");
                 Zotero_DB::close();
                 self::removeUploadProcess($processID);
             } else {
                 throw $e;
             }
         }
         // Send notifications for changed libraries
         foreach ($affectedLibraries as $libraryID) {
             Zotero_Notifier::trigger('modify', 'library', $libraryID);
         }
         Zotero_DB::commit();
         if ($profile) {
             $shardID = Zotero_Shards::getByUserID($userID);
             Zotero_DB::profileEnd($shardID);
         }
         // Return timestamp + 1, to keep the next /updated call
         // (using >= timestamp) from returning this data
         return $timestamp + 1;
     } catch (Exception $e) {
         Zotero_DB::rollback(true);
         self::removeUploadProcess($processID);
         throw $e;
     }
 }
Ejemplo n.º 10
0
 /**
  * Returns all tags assigned to items in this collection
  */
 public function getTags($asIDs = false)
 {
     $sql = "SELECT tagID FROM tags JOIN itemTags USING (tagID)\n\t\t\t\tJOIN collectionItems USING (itemID) WHERE collectionID=? ORDER BY name";
     $tagIDs = Zotero_DB::columnQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
     if (!$tagIDs) {
         return false;
     }
     if ($asIDs) {
         return $tagIDs;
     }
     $tagObjs = array();
     foreach ($tagIDs as $tagID) {
         $tag = Zotero_Tags::get($tagID, true);
         $tagObjs[] = $tag;
     }
     return $tagObjs;
 }
Ejemplo n.º 11
0
 private static function processUploadInternal($userID, SimpleXMLElement $xml, $syncQueueID = null, $syncProcessID = null)
 {
     $userLibraryID = Zotero_Users::getLibraryIDFromUserID($userID);
     $affectedLibraries = self::parseAffectedLibraries($xml->asXML());
     // Relations-only uploads don't have affected libraries
     if (!$affectedLibraries) {
         $affectedLibraries = array(Zotero_Users::getLibraryIDFromUserID($userID));
     }
     $processID = self::addUploadProcess($userID, $affectedLibraries, $syncQueueID, $syncProcessID);
     set_time_limit(5400);
     $profile = false;
     if ($profile) {
         $shardID = Zotero_Shards::getByUserID($userID);
         Zotero_DB::profileStart($shardID);
     }
     try {
         Z_Core::$MC->begin();
         Zotero_DB::beginTransaction();
         // Mark libraries as updated
         $timestamp = Zotero_Libraries::updateTimestamps($affectedLibraries);
         Zotero_DB::registerTransactionTimestamp($timestamp);
         // Make sure no other upload sessions use this same timestamp
         // for any of these libraries, since we return >= 1 as the next
         // last sync time
         if (!Zotero_Libraries::setTimestampLock($affectedLibraries, $timestamp)) {
             throw new Exception("Library timestamp already used", Z_ERROR_LIBRARY_TIMESTAMP_ALREADY_USED);
         }
         // Add/update creators
         if ($xml->creators) {
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->creators);
             $xmlElements = $xmlElements->getElementsByTagName('creator');
             Zotero_DB::query("SET foreign_key_checks = 0");
             try {
                 $addedLibraryIDs = array();
                 $addedCreatorDataHashes = array();
                 foreach ($xmlElements as $xmlElement) {
                     $key = $xmlElement->getAttribute('key');
                     if (isset($keys[$key])) {
                         throw new Exception("Creator {$key} already processed");
                     }
                     $keys[$key] = true;
                     $creatorObj = Zotero_Creators::convertXMLToCreator($xmlElement);
                     $addedLibraryIDs[] = $creatorObj->libraryID;
                     $creatorObj->save();
                 }
             } catch (Exception $e) {
                 Zotero_DB::query("SET foreign_key_checks = 1");
                 throw $e;
             }
             Zotero_DB::query("SET foreign_key_checks = 1");
             unset($keys);
             unset($xml->creators);
             //
             // Manual foreign key checks
             //
             // libraryID
             foreach ($addedLibraryIDs as $addedLibraryID) {
                 $shardID = Zotero_Shards::getByLibraryID($addedLibraryID);
                 $sql = "SELECT COUNT(*) FROM shardLibraries WHERE libraryID=?";
                 if (!Zotero_DB::valueQuery($sql, $addedLibraryID, $shardID)) {
                     throw new Exception("libraryID inserted into `creators` not found in `shardLibraries` ({$addedLibraryID}, {$shardID})");
                 }
             }
         }
         // Add/update items
         if ($xml->items) {
             $childItems = array();
             $relatedItemsStore = array();
             // DOM
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->items);
             $xmlElements = $xmlElements->getElementsByTagName('item');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Item {$key} already processed");
                 }
                 $keys[$key] = true;
                 $missing = Zotero_Items::removeMissingRelatedItems($xmlElement);
                 $itemObj = Zotero_Items::convertXMLToItem($xmlElement);
                 if ($missing) {
                     $relatedItemsStore[$itemObj->libraryID . '_' . $itemObj->key] = $missing;
                 }
                 if (!$itemObj->getSourceKey()) {
                     try {
                         $itemObj->save($userID);
                     } catch (Exception $e) {
                         if (strpos($e->getMessage(), 'libraryIDs_do_not_match') !== false) {
                             throw new Exception($e->getMessage() . " (" . $itemObj->key . ")");
                         }
                         throw $e;
                     }
                 } else {
                     $childItems[] = $itemObj;
                 }
             }
             unset($keys);
             unset($xml->items);
             while ($childItem = array_shift($childItems)) {
                 $childItem->save($userID);
             }
             // Add back related items (which now exist)
             foreach ($relatedItemsStore as $itemLibraryKey => $relset) {
                 $lk = explode('_', $itemLibraryKey);
                 $libraryID = $lk[0];
                 $key = $lk[1];
                 $item = Zotero_Items::getByLibraryAndKey($libraryID, $key);
                 foreach ($relset as $relKey) {
                     $relItem = Zotero_Items::getByLibraryAndKey($libraryID, $relKey);
                     $item->addRelatedItem($relItem->id);
                 }
                 $item->save();
             }
             unset($relatedItemsStore);
         }
         // Add/update collections
         if ($xml->collections) {
             $collections = array();
             $collectionSets = array();
             // DOM
             // Build an array of unsaved collection objects and the keys of child items
             $keys = array();
             $xmlElements = dom_import_simplexml($xml->collections);
             $xmlElements = $xmlElements->getElementsByTagName('collection');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Collection {$key} already processed");
                 }
                 $keys[$key] = true;
                 $collectionObj = Zotero_Collections::convertXMLToCollection($xmlElement);
                 $xmlItems = $xmlElement->getElementsByTagName('items')->item(0);
                 // Fix an error if there's leading or trailing whitespace,
                 // which was possible in 2.0.3
                 if ($xmlItems) {
                     $xmlItems = trim($xmlItems->nodeValue);
                 }
                 $arr = array('obj' => $collectionObj, 'items' => $xmlItems ? explode(' ', $xmlItems) : array());
                 $collections[] = $collectionObj;
                 $collectionSets[] = $arr;
             }
             unset($keys);
             unset($xml->collections);
             self::saveCollections($collections);
             unset($collections);
             // Set child items
             foreach ($collectionSets as $collection) {
                 // Child items
                 if (isset($collection['items'])) {
                     $ids = array();
                     foreach ($collection['items'] as $key) {
                         $item = Zotero_Items::getByLibraryAndKey($collection['obj']->libraryID, $key);
                         if (!$item) {
                             throw new Exception("Child item '{$key}' of collection {$collection['obj']->id} not found", Z_ERROR_ITEM_NOT_FOUND);
                         }
                         $ids[] = $item->id;
                     }
                     $collection['obj']->setChildItems($ids);
                 }
             }
             unset($collectionSets);
         }
         // Add/update saved searches
         if ($xml->searches) {
             $searches = array();
             $keys = array();
             foreach ($xml->searches->search as $xmlElement) {
                 $key = (string) $xmlElement['key'];
                 if (isset($keys[$key])) {
                     throw new Exception("Search {$key} already processed");
                 }
                 $keys[$key] = true;
                 $searchObj = Zotero_Searches::convertXMLToSearch($xmlElement);
                 $searchObj->save();
             }
             unset($xml->searches);
         }
         // Add/update tags
         if ($xml->tags) {
             $keys = array();
             // DOM
             $xmlElements = dom_import_simplexml($xml->tags);
             $xmlElements = $xmlElements->getElementsByTagName('tag');
             foreach ($xmlElements as $xmlElement) {
                 $key = $xmlElement->getAttribute('key');
                 if (isset($keys[$key])) {
                     throw new Exception("Tag {$key} already processed");
                 }
                 $keys[$key] = true;
                 $tagObj = Zotero_Tags::convertXMLToTag($xmlElement);
                 $tagObj->save(true);
             }
             unset($keys);
             unset($xml->tags);
         }
         // Add/update relations
         if ($xml->relations) {
             // DOM
             $xmlElements = dom_import_simplexml($xml->relations);
             $xmlElements = $xmlElements->getElementsByTagName('relation');
             foreach ($xmlElements as $xmlElement) {
                 $relationObj = Zotero_Relations::convertXMLToRelation($xmlElement, $userLibraryID);
                 if ($relationObj->exists()) {
                     continue;
                 }
                 $relationObj->save();
             }
             unset($keys);
             unset($xml->relations);
         }
         // TODO: loop
         if ($xml->deleted) {
             // Delete collections
             if ($xml->deleted->collections) {
                 Zotero_Collections::deleteFromXML($xml->deleted->collections);
             }
             // Delete items
             if ($xml->deleted->items) {
                 Zotero_Items::deleteFromXML($xml->deleted->items);
             }
             // Delete creators
             if ($xml->deleted->creators) {
                 Zotero_Creators::deleteFromXML($xml->deleted->creators);
             }
             // Delete saved searches
             if ($xml->deleted->searches) {
                 Zotero_Searches::deleteFromXML($xml->deleted->searches);
             }
             // Delete tags
             if ($xml->deleted->tags) {
                 Zotero_Tags::deleteFromXML($xml->deleted->tags);
             }
             // Delete tags
             if ($xml->deleted->relations) {
                 Zotero_Relations::deleteFromXML($xml->deleted->relations);
             }
         }
         self::removeUploadProcess($processID);
         Zotero_DB::commit();
         Z_Core::$MC->commit();
         if ($profile) {
             $shardID = Zotero_Shards::getByUserID($userID);
             Zotero_DB::profileEnd($shardID);
         }
         // Return timestamp + 1, to keep the next /updated call
         // (using >= timestamp) from returning this data
         return $timestamp + 1;
     } catch (Exception $e) {
         Z_Core::$MC->rollback();
         Zotero_DB::rollback(true);
         self::removeUploadProcess($processID);
         throw $e;
     }
 }
Ejemplo n.º 12
0
 public static function search($libraryID, $onlyTopLevel = false, $params = array(), $includeTrashed = false, $asKeys = false)
 {
     $rnd = "_" . uniqid($libraryID . "_");
     if ($asKeys) {
         $results = array('keys' => array(), 'total' => 0);
     } else {
         $results = array('items' => array(), 'total' => 0);
     }
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $itemIDs = array();
     $keys = array();
     $deleteTempTable = array();
     // Pass a list of itemIDs, for when the initial search is done via SQL
     if (!empty($params['itemIDs'])) {
         $itemIDs = $params['itemIDs'];
     }
     if (!empty($params['itemKey'])) {
         $keys = explode(',', $params['itemKey']);
     }
     $titleSort = !empty($params['order']) && $params['order'] == 'title';
     $sql = "SELECT SQL_CALC_FOUND_ROWS DISTINCT " . ($asKeys ? "I.key" : "I.itemID") . " FROM items I ";
     $sqlParams = array($libraryID);
     if (!empty($params['q']) || $titleSort) {
         $titleFieldIDs = array_merge(array(Zotero_ItemFields::getID('title')), Zotero_ItemFields::getTypeFieldsFromBase('title'));
         $sql .= "LEFT JOIN itemData IDT ON (IDT.itemID=I.itemID AND IDT.fieldID IN (" . implode(',', $titleFieldIDs) . ")) ";
     }
     if (!empty($params['q'])) {
         $sql .= "LEFT JOIN itemCreators IC ON (IC.itemID=I.itemID)\n\t\t\t\t\tLEFT JOIN creators C ON (C.creatorID=IC.creatorID) ";
     }
     if ($onlyTopLevel || !empty($params['q']) || $titleSort) {
         $sql .= "LEFT JOIN itemNotes INo ON (INo.itemID=I.itemID) ";
     }
     if ($onlyTopLevel) {
         $sql .= "LEFT JOIN itemAttachments IA ON (IA.itemID=I.itemID) ";
     }
     if (!$includeTrashed) {
         $sql .= "LEFT JOIN deletedItems DI ON (DI.itemID=I.itemID) ";
     }
     if (!empty($params['order'])) {
         switch ($params['order']) {
             case 'title':
             case 'creator':
                 $sql .= "LEFT JOIN itemSortFields ISF ON (ISF.itemID=I.itemID) ";
                 break;
             case 'date':
                 $dateFieldIDs = array_merge(array(Zotero_ItemFields::getID('date')), Zotero_ItemFields::getTypeFieldsFromBase('date'));
                 $sql .= "LEFT JOIN itemData IDD ON (IDD.itemID=I.itemID AND IDD.fieldID IN (" . implode(',', $dateFieldIDs) . ")) ";
                 break;
             case 'itemType':
                 // Create temporary table to store item type names
                 //
                 // We use IF NOT EXISTS just to make sure there are
                 // no problems with restoration from the binary log
                 $sql2 = "CREATE TEMPORARY TABLE IF NOT EXISTS tmpItemTypeNames{$rnd}\n\t\t\t\t\t\t\t(itemTypeID SMALLINT UNSIGNED NOT NULL,\n\t\t\t\t\t\t\titemTypeName VARCHAR(255) NOT NULL,\n\t\t\t\t\t\t\tPRIMARY KEY (itemTypeID),\n\t\t\t\t\t\t\tINDEX (itemTypeName))";
                 Zotero_DB::query($sql2, false, $shardID);
                 $deleteTempTable['tmpItemTypeNames'] = true;
                 $types = Zotero_ItemTypes::getAll('en-US');
                 foreach ($types as $type) {
                     $sql2 = "INSERT INTO tmpItemTypeNames{$rnd} VALUES (?, ?)";
                     Zotero_DB::query($sql2, array($type['id'], $type['localized']), $shardID);
                 }
                 // Join temp table to query
                 $sql .= "JOIN tmpItemTypeNames{$rnd} TITN ON (TITN.itemTypeID=I.itemTypeID) ";
                 break;
             case 'addedBy':
                 $isGroup = Zotero_Libraries::getType($libraryID) == 'group';
                 if ($isGroup) {
                     // Create temporary table to store usernames
                     //
                     // We use IF NOT EXISTS just to make sure there are
                     // no problems with restoration from the binary log
                     $sql2 = "CREATE TEMPORARY TABLE IF NOT EXISTS tmpCreatedByUsers{$rnd}\n\t\t\t\t\t\t\t\t(userID INT UNSIGNED NOT NULL,\n\t\t\t\t\t\t\t\tusername VARCHAR(255) NOT NULL,\n\t\t\t\t\t\t\t\tPRIMARY KEY (userID),\n\t\t\t\t\t\t\t\tINDEX (username))";
                     Zotero_DB::query($sql2, false, $shardID);
                     $deleteTempTable['tmpCreatedByUsers'] = true;
                     $sql2 = "SELECT DISTINCT createdByUserID FROM items\n\t\t\t\t\t\t\t\tJOIN groupItems USING (itemID) WHERE\n\t\t\t\t\t\t\t\tcreatedByUserID IS NOT NULL AND ";
                     if ($itemIDs) {
                         $sql2 .= "itemID IN (" . implode(', ', array_fill(0, sizeOf($itemIDs), '?')) . ") ";
                         $createdByUserIDs = Zotero_DB::columnQuery($sql2, $itemIDs, $shardID);
                     } else {
                         $sql2 .= "libraryID=?";
                         $createdByUserIDs = Zotero_DB::columnQuery($sql2, $libraryID, $shardID);
                     }
                     // Populate temp table with usernames
                     if ($createdByUserIDs) {
                         $toAdd = array();
                         foreach ($createdByUserIDs as $createdByUserID) {
                             $toAdd[] = array($createdByUserID, Zotero_Users::getUsername($createdByUserID));
                         }
                         $sql2 = "INSERT IGNORE INTO tmpCreatedByUsers{$rnd} VALUES ";
                         Zotero_DB::bulkInsert($sql2, $toAdd, 50, false, $shardID);
                         // Join temp table to query
                         $sql .= "JOIN groupItems GI ON (GI.itemID=I.itemID)\n\t\t\t\t\t\t\t\t\tJOIN tmpCreatedByUsers{$rnd} TCBU ON (TCBU.userID=GI.createdByUserID) ";
                     }
                 }
                 break;
         }
     }
     $sql .= "WHERE I.libraryID=? ";
     if ($onlyTopLevel) {
         $sql .= "AND INo.sourceItemID IS NULL AND IA.sourceItemID IS NULL ";
     }
     if (!$includeTrashed) {
         $sql .= "AND DI.itemID IS NULL ";
     }
     // Search on title and creators
     if (!empty($params['q'])) {
         $sql .= "AND (";
         $sql .= "IDT.value LIKE ? ";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= "OR title LIKE ? ";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= "OR TRIM(CONCAT(firstName, ' ', lastName)) LIKE ?";
         $sqlParams[] = '%' . $params['q'] . '%';
         $sql .= ") ";
     }
     // Search on itemType
     if (!empty($params['itemType'])) {
         $itemTypes = Zotero_API::getSearchParamValues($params, 'itemType');
         if ($itemTypes) {
             if (sizeOf($itemTypes) > 1) {
                 throw new Exception("Cannot specify 'itemType' more than once", Z_ERROR_INVALID_INPUT);
             }
             $itemTypes = $itemTypes[0];
             $itemTypeIDs = array();
             foreach ($itemTypes['values'] as $itemType) {
                 $itemTypeID = Zotero_ItemTypes::getID($itemType);
                 if (!$itemTypeID) {
                     throw new Exception("Invalid itemType '{$itemType}'", Z_ERROR_INVALID_INPUT);
                 }
                 $itemTypeIDs[] = $itemTypeID;
             }
             $sql .= "AND I.itemTypeID " . ($itemTypes['negation'] ? "NOT " : "") . "IN (" . implode(',', array_fill(0, sizeOf($itemTypeIDs), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $itemTypeIDs);
         }
     }
     // Tags
     //
     // ?tag=foo
     // ?tag=foo bar // phrase
     // ?tag=-foo // negation
     // ?tag=\-foo // literal hyphen (only for first character)
     // ?tag=foo&tag=bar // AND
     // ?tag=foo&tagType=0
     // ?tag=foo bar || bar&tagType=0
     $tagSets = Zotero_API::getSearchParamValues($params, 'tag');
     if ($tagSets) {
         $sql2 = "SELECT itemID FROM items WHERE 1 ";
         $sqlParams2 = array();
         if ($tagSets) {
             foreach ($tagSets as $set) {
                 $positives = array();
                 $negatives = array();
                 $tagIDs = array();
                 foreach ($set['values'] as $tag) {
                     $ids = Zotero_Tags::getIDs($libraryID, $tag);
                     if (!$ids) {
                         $ids = array(0);
                     }
                     $tagIDs = array_merge($tagIDs, $ids);
                 }
                 $tagIDs = array_unique($tagIDs);
                 if ($set['negation']) {
                     $negatives = array_merge($negatives, $tagIDs);
                 } else {
                     $positives = array_merge($positives, $tagIDs);
                 }
                 if ($positives) {
                     $sql2 .= "AND itemID IN (SELECT itemID FROM items JOIN itemTags USING (itemID)\n\t\t\t\t\t\t\t\tWHERE tagID IN (" . implode(',', array_fill(0, sizeOf($positives), '?')) . ")) ";
                     $sqlParams2 = array_merge($sqlParams2, $positives);
                 }
                 if ($negatives) {
                     $sql2 .= "AND itemID NOT IN (SELECT itemID FROM items JOIN itemTags USING (itemID)\n\t\t\t\t\t\t\t\tWHERE tagID IN (" . implode(',', array_fill(0, sizeOf($negatives), '?')) . ")) ";
                     $sqlParams2 = array_merge($sqlParams2, $negatives);
                 }
             }
         }
         $tagItems = Zotero_DB::columnQuery($sql2, $sqlParams2, $shardID);
         // No matches
         if (!$tagItems) {
             return $results;
         }
         // Combine with passed keys
         if ($itemIDs) {
             $itemIDs = array_intersect($itemIDs, $tagItems);
             // None of the tag matches match the passed keys
             if (!$itemIDs) {
                 return $results;
             }
         } else {
             $itemIDs = $tagItems;
         }
     }
     if ($itemIDs) {
         $sql .= "AND I.itemID IN (" . implode(', ', array_fill(0, sizeOf($itemIDs), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $itemIDs);
     }
     if ($keys) {
         $sql .= "AND `key` IN (" . implode(', ', array_fill(0, sizeOf($keys), '?')) . ") ";
         $sqlParams = array_merge($sqlParams, $keys);
     }
     $sql .= "ORDER BY ";
     if (!empty($params['order'])) {
         switch ($params['order']) {
             case 'dateAdded':
             case 'dateModified':
             case 'serverDateModified':
                 $orderSQL = "I." . $params['order'];
                 break;
             case 'itemType':
                 $orderSQL = "TITN.itemTypeName";
                 break;
             case 'title':
                 $orderSQL = "IFNULL(COALESCE(sortTitle, IDT.value, INo.title), '')";
                 break;
             case 'creator':
                 $orderSQL = "ISF.creatorSummary";
                 break;
                 // TODO: generic base field mapping-aware sorting
             // TODO: generic base field mapping-aware sorting
             case 'date':
                 $orderSQL = "IDD.value";
                 break;
             case 'addedBy':
                 if ($isGroup && $createdByUserIDs) {
                     $orderSQL = "TCBU.username";
                 } else {
                     $orderSQL = "1";
                 }
                 break;
             default:
                 $fieldID = Zotero_ItemFields::getID($params['order']);
                 if (!$fieldID) {
                     throw new Exception("Invalid order field '" . $params['order'] . "'");
                 }
                 $orderSQL = "(SELECT value FROM itemData WHERE itemID=I.itemID AND fieldID=?)";
                 if (!$params['emptyFirst']) {
                     $sqlParams[] = $fieldID;
                 }
                 $sqlParams[] = $fieldID;
         }
         if (!empty($params['sort'])) {
             $dir = $params['sort'];
         } else {
             $dir = "ASC";
         }
         if (!$params['emptyFirst']) {
             $sql .= "IFNULL({$orderSQL}, '') = '' {$dir}, ";
         }
         $sql .= $orderSQL;
         $sql .= " {$dir}, ";
     }
     $sql .= "I.itemID " . (!empty($params['sort']) ? $params['sort'] : "ASC") . " ";
     if (!empty($params['limit'])) {
         $sql .= "LIMIT ?, ?";
         $sqlParams[] = $params['start'] ? $params['start'] : 0;
         $sqlParams[] = $params['limit'];
     }
     $itemIDs = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
     $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
     if ($itemIDs) {
         if ($asKeys) {
             $results['keys'] = $itemIDs;
         } else {
             $results['items'] = Zotero_Items::get($libraryID, $itemIDs);
         }
     }
     if (!empty($deleteTempTable['tmpCreatedByUsers'])) {
         $sql = "DROP TEMPORARY TABLE IF EXISTS tmpCreatedByUsers{$rnd}";
         Zotero_DB::query($sql, false, $shardID);
     }
     if (!empty($deleteTempTable['tmpItemTypeNames'])) {
         $sql = "DROP TEMPORARY TABLE IF EXISTS tmpItemTypeNames{$rnd}";
         Zotero_DB::query($sql, false, $shardID);
     }
     return $results;
 }
Ejemplo n.º 13
0
 public static function getAllAdvanced($libraryID, $params)
 {
     $results = array('objects' => array(), 'total' => 0);
     $sql = "SELECT SQL_CALC_FOUND_ROWS tagID FROM tags ";
     if (!empty($params['order']) && $params['order'] == 'numItems') {
         $sql .= " LEFT JOIN itemTags USING (tagID)";
     }
     $sql .= "WHERE libraryID=? ";
     $sqlParams = array($libraryID);
     if (!empty($params['q'])) {
         if (!is_array($params['q'])) {
             $params['q'] = array($params['q']);
         }
         foreach ($params['q'] as $q) {
             $sql .= "AND name LIKE ? ";
             $sqlParams[] = "%{$q}%";
         }
     }
     $tagTypeSets = Zotero_API::getSearchParamValues($params, 'tagType');
     if ($tagTypeSets) {
         $positives = array();
         $negatives = array();
         foreach ($tagTypeSets as $set) {
             if ($set['negation']) {
                 $negatives = array_merge($negatives, $set['values']);
             } else {
                 $positives = array_merge($positives, $set['values']);
             }
         }
         if ($positives) {
             $sql .= "AND type IN (" . implode(',', array_fill(0, sizeOf($positives), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $positives);
         }
         if ($negatives) {
             $sql .= "AND type NOT IN (" . implode(',', array_fill(0, sizeOf($negatives), '?')) . ") ";
             $sqlParams = array_merge($sqlParams, $negatives);
         }
     }
     if (!empty($params['order'])) {
         $order = $params['order'];
         if ($order == 'title') {
             // Force a case-insensitive sort
             $sql .= "ORDER BY name COLLATE utf8_unicode_ci ";
         } else {
             if ($order == 'numItems') {
                 $sql .= "GROUP BY tags.tagID ORDER BY COUNT(tags.tagID)";
             } else {
                 $sql .= "ORDER BY {$order} ";
             }
         }
         if (!empty($params['sort'])) {
             $sql .= " " . $params['sort'] . " ";
         }
     }
     if (!empty($params['limit'])) {
         $sql .= "LIMIT ?, ?";
         $sqlParams[] = $params['start'] ? $params['start'] : 0;
         $sqlParams[] = $params['limit'];
     }
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $ids = Zotero_DB::columnQuery($sql, $sqlParams, $shardID);
     if ($ids) {
         $results['total'] = Zotero_DB::valueQuery("SELECT FOUND_ROWS()", false, $shardID);
         $tags = array();
         foreach ($ids as $id) {
             $tags[] = Zotero_Tags::get($libraryID, $id);
         }
         $results['objects'] = $tags;
     }
     return $results;
 }
Ejemplo n.º 14
0
 public function items()
 {
     // Check for general library access
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     if ($this->isWriteMethod()) {
         // Check for library write access
         if (!$this->permissions->canWrite($this->objectLibraryID)) {
             $this->e403("Write access denied");
         }
         // Make sure library hasn't been modified
         if (!$this->singleObject) {
             $libraryTimestampChecked = $this->checkLibraryIfUnmodifiedSinceVersion();
         }
         // We don't update the library version in file mode, because currently
         // to avoid conflicts in the client the timestamp can't change
         // when the client updates file metadata
         if (!$this->fileMode) {
             Zotero_Libraries::updateVersionAndTimestamp($this->objectLibraryID);
         }
     }
     $itemIDs = array();
     $itemKeys = array();
     $results = array();
     $title = "";
     //
     // Single item
     //
     if ($this->singleObject) {
         if ($this->fileMode) {
             if ($this->fileView) {
                 $this->allowMethods(array('HEAD', 'GET', 'POST'));
             } else {
                 $this->allowMethods(array('HEAD', 'GET', 'PUT', 'POST', 'PATCH'));
             }
         } else {
             $this->allowMethods(array('HEAD', 'GET', 'PUT', 'PATCH', 'DELETE'));
         }
         if (!$this->objectLibraryID || !Zotero_ID::isValidKey($this->objectKey)) {
             $this->e404();
         }
         $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectKey);
         if ($item) {
             // If no access to the note, don't show that it exists
             if ($item->isNote() && !$this->permissions->canAccess($this->objectLibraryID, 'notes')) {
                 $this->e404();
             }
             // 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 ($this->scopeObject) {
                 switch ($this->scopeObject) {
                     // Remove item from collection
                     case 'collections':
                         $this->allowMethods(array('DELETE'));
                         $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");
                         }
                         $collection->removeItem($item->id);
                         $this->e204();
                     default:
                         $this->e400();
                 }
             }
         } else {
             // Possibly temporary workaround to block unnecessary full syncs
             if ($this->fileMode && $this->httpAuth && $this->method == 'POST') {
                 // If > 2 requests for missing file, trigger a full sync via 404
                 $cacheKey = "apiMissingFile_" . $this->objectLibraryID . "_" . $this->objectKey;
                 $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 ($this->isWriteMethod()) {
             $item = $this->handleObjectWrite('item', $item ? $item : null);
             if ($this->apiVersion < 2 && ($this->method == 'PUT' || $this->method == 'PATCH')) {
                 $this->queryParams['format'] = 'atom';
                 $this->queryParams['content'] = ['json'];
             }
         }
         if (!$item) {
             $this->e404("Item does not exist");
         }
         $this->libraryVersion = $item->version;
         if ($this->method == 'HEAD') {
             $this->end();
         }
         // Display item
         switch ($this->queryParams['format']) {
             case 'atom':
                 $this->responseXML = Zotero_Items::convertItemToAtom($item, $this->queryParams, $this->permissions);
                 break;
             case 'bib':
                 echo Zotero_Cite::getBibliographyFromCitationServer(array($item), $this->queryParams);
                 break;
             case 'csljson':
                 $json = Zotero_Cite::getJSONFromItems(array($item), true);
                 echo Zotero_Utilities::formatJSON($json);
                 break;
             case 'json':
                 $json = $item->toResponseJSON($this->queryParams, $this->permissions);
                 echo Zotero_Utilities::formatJSON($json);
                 break;
             default:
                 $export = Zotero_Translate::doExport(array($item), $this->queryParams['format']);
                 $this->queryParams['format'] = null;
                 header("Content-Type: " . $export['mimeType']);
                 echo $export['body'];
                 break;
         }
     } else {
         $this->allowMethods(array('HEAD', 'GET', 'POST', 'DELETE'));
         $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
         $includeTrashed = $this->queryParams['includeTrashed'];
         if ($this->scopeObject) {
             $this->allowMethods(array('GET', 'POST'));
             switch ($this->scopeObject) {
                 case 'collections':
                     // TEMP
                     if (Zotero_ID::isValidKey($this->scopeObjectKey)) {
                         $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                     } else {
                         $collection = false;
                     }
                     if (!$collection) {
                         // If old collectionID, redirect
                         if ($this->method == 'GET' && Zotero_Utilities::isPosInt($this->scopeObjectKey)) {
                             $collection = Zotero_Collections::get($this->objectLibraryID, $this->scopeObjectKey);
                             if ($collection) {
                                 $qs = !empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : '';
                                 $base = Zotero_API::getCollectionURI($collection);
                                 $this->redirect($base . "/items" . $qs, 301);
                             }
                         }
                         $this->e404("Collection not found");
                     }
                     // Add items to collection
                     if ($this->method == 'POST') {
                         $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);
                         $this->e204();
                     }
                     if ($this->subset == 'top' || $this->apiVersion < 2) {
                         $title = "Top-Level Items in Collection ‘" . $collection->name . "’";
                         $itemIDs = $collection->getItems();
                     } else {
                         $title = "Items in Collection ‘" . $collection->name . "’";
                         $itemIDs = $collection->getItems(true);
                     }
                     break;
                 case 'tags':
                     if ($this->apiVersion >= 2) {
                         $this->e404();
                     }
                     $this->allowMethods(array('GET'));
                     $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $this->scopeObjectName);
                     if (!$tagIDs) {
                         $this->e404("Tag not found");
                     }
                     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 . "’";
                         }
                         $itemKeys = array_merge($itemKeys, $tag->getLinkedItems(true));
                     }
                     $itemKeys = array_unique($itemKeys);
                     break;
                 default:
                     $this->e404();
             }
         } 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, $includeTrashed, $this->permissions);
             } else {
                 if ($this->subset == 'trash') {
                     $this->allowMethods(array('GET'));
                     $title = "Deleted Items";
                     $this->queryParams['trashedItemsOnly'] = true;
                     $includeTrashed = true;
                     $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
                 } else {
                     if ($this->subset == 'children') {
                         $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->objectKey);
                         if (!$item) {
                             $this->e404("Item not found");
                         }
                         if ($item->isAttachment()) {
                             $this->e400("/children cannot be called on attachment items");
                         }
                         if ($item->isNote()) {
                             $this->e400("/children cannot be called on note items");
                         }
                         if ($item->getSource()) {
                             $this->e400("/children cannot be called on child items");
                         }
                         // Create new child items
                         if ($this->method == 'POST') {
                             if ($this->apiVersion >= 2) {
                                 $this->allowMethods(array('GET'));
                             }
                             Zotero_DB::beginTransaction();
                             $obj = $this->jsonDecode($this->body);
                             $results = Zotero_Items::updateMultipleFromJSON($obj, $this->queryParams, $this->objectLibraryID, $this->userID, $this->permissions, $libraryTimestampChecked ? 0 : 1, $item);
                             Zotero_DB::commit();
                             if ($cacheKey = $this->getWriteTokenCacheKey()) {
                                 Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime);
                             }
                             $uri = Zotero_API::getItemsURI($this->objectLibraryID);
                             $keys = array_merge(get_object_vars($results['success']), get_object_vars($results['unchanged']));
                             $queryString = "itemKey=" . urlencode(implode(",", $keys)) . "&format=atom&content=json&order=itemKeyList&sort=asc";
                             if ($this->apiKey) {
                                 $queryString .= "&key=" . $this->apiKey;
                             }
                             $uri .= "?" . $queryString;
                             $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, false);
                             $this->responseCode = 201;
                             $title = "Items";
                             $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
                         } else {
                             $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') {
                             $this->queryParams['format'] = 'writereport';
                             $obj = $this->jsonDecode($this->body);
                             // Server-side translation
                             if (isset($obj->url)) {
                                 if ($this->apiVersion == 1) {
                                     Zotero_DB::beginTransaction();
                                 }
                                 $token = $this->getTranslationToken($obj);
                                 $results = Zotero_Items::addFromURL($obj, $this->queryParams, $this->objectLibraryID, $this->userID, $this->permissions, $token);
                                 if ($this->apiVersion == 1) {
                                     Zotero_DB::commit();
                                 }
                                 // Multiple choices
                                 if ($results instanceof stdClass) {
                                     $this->queryParams['format'] = null;
                                     header("Content-Type: application/json");
                                     if ($this->queryParams['v'] >= 2) {
                                         echo Zotero_Utilities::formatJSON(['url' => $obj->url, 'token' => $token, 'items' => $results->select]);
                                     } else {
                                         echo Zotero_Utilities::formatJSON($results->select);
                                     }
                                     $this->e300();
                                 } else {
                                     if (is_int($results)) {
                                         switch ($results) {
                                             case 501:
                                                 $this->e501("No translators found for URL");
                                                 break;
                                             default:
                                                 $this->e500("Error translating URL");
                                         }
                                     } else {
                                         if ($this->apiVersion == 1) {
                                             $uri = Zotero_API::getItemsURI($this->objectLibraryID);
                                             $keys = array_merge(get_object_vars($results['success']), get_object_vars($results['unchanged']));
                                             $queryString = "itemKey=" . urlencode(implode(",", $keys)) . "&format=atom&content=json&order=itemKeyList&sort=asc";
                                             if ($this->apiKey) {
                                                 $queryString .= "&key=" . $this->apiKey;
                                             }
                                             $uri .= "?" . $queryString;
                                             $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, false);
                                             $this->responseCode = 201;
                                             $title = "Items";
                                             $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
                                         }
                                     }
                                 }
                                 // Otherwise return write status report
                             } else {
                                 if ($this->apiVersion < 2) {
                                     Zotero_DB::beginTransaction();
                                 }
                                 $results = Zotero_Items::updateMultipleFromJSON($obj, $this->queryParams, $this->objectLibraryID, $this->userID, $this->permissions, $libraryTimestampChecked ? 0 : 1, null);
                                 if ($this->apiVersion < 2) {
                                     Zotero_DB::commit();
                                     $uri = Zotero_API::getItemsURI($this->objectLibraryID);
                                     $keys = array_merge(get_object_vars($results['success']), get_object_vars($results['unchanged']));
                                     $queryString = "itemKey=" . urlencode(implode(",", $keys)) . "&format=atom&content=json&order=itemKeyList&sort=asc";
                                     if ($this->apiKey) {
                                         $queryString .= "&key=" . $this->apiKey;
                                     }
                                     $uri .= "?" . $queryString;
                                     $this->queryParams = Zotero_API::parseQueryParams($queryString, $this->action, false);
                                     $this->responseCode = 201;
                                     $title = "Items";
                                     $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
                                 }
                             }
                             if ($cacheKey = $this->getWriteTokenCacheKey()) {
                                 Z_Core::$MC->set($cacheKey, true, $this->writeTokenCacheTime);
                             }
                         } else {
                             if ($this->method == 'DELETE') {
                                 Zotero_DB::beginTransaction();
                                 foreach ($this->queryParams['itemKey'] as $itemKey) {
                                     Zotero_Items::delete($this->objectLibraryID, $itemKey);
                                 }
                                 Zotero_DB::commit();
                                 $this->e204();
                             } else {
                                 $title = "Items";
                                 $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
                             }
                         }
                     }
                 }
             }
         }
         if ($itemIDs || $itemKeys) {
             if ($itemIDs) {
                 $this->queryParams['itemIDs'] = $itemIDs;
             }
             if ($itemKeys) {
                 $this->queryParams['itemKey'] = $itemKeys;
             }
             $results = Zotero_Items::search($this->objectLibraryID, false, $this->queryParams, $includeTrashed, $this->permissions);
         }
         if ($this->queryParams['format'] == 'bib') {
             $maxBibItems = Zotero_API::MAX_BIBLIOGRAPHY_ITEMS;
             if ($results['total'] > $maxBibItems) {
                 $this->e413("Cannot generate bibliography with more than {$maxBibItems} items");
             }
         }
         $this->generateMultiResponse($results, $title);
     }
     $this->end();
 }
Ejemplo n.º 15
0
 private function loadTags()
 {
     if (!$this->id) {
         return;
     }
     Z_Core::debug("Loading tags for item {$this->id}");
     if ($this->loaded['tags']) {
         throw new Exception("Tags already loaded for item {$this->id}");
     }
     $sql = "SELECT tagID FROM itemTags JOIN tags USING (tagID) WHERE itemID=?";
     $tagIDs = Zotero_DB::columnQuery($sql, $this->id, Zotero_Shards::getByLibraryID($this->libraryID));
     $this->tags = [];
     if ($tagIDs) {
         foreach ($tagIDs as $tagID) {
             $this->tags[] = Zotero_Tags::get($this->libraryID, $tagID, true);
         }
     }
     $this->loaded['tags'] = true;
 }
Ejemplo n.º 16
0
 public function tags()
 {
     $this->allowMethods(['HEAD', 'GET', 'DELETE']);
     if (!$this->permissions->canAccess($this->objectLibraryID)) {
         $this->e403();
     }
     if ($this->isWriteMethod()) {
         // Check for library write access
         if (!$this->permissions->canWrite($this->objectLibraryID)) {
             $this->e403("Write access denied");
         }
         // Make sure library hasn't been modified
         $this->checkLibraryIfUnmodifiedSinceVersion(true);
         Zotero_Libraries::updateVersionAndTimestamp($this->objectLibraryID);
     }
     $tagIDs = array();
     $results = array();
     $name = $this->objectName;
     $fixedValues = array();
     $this->libraryVersion = Zotero_Libraries::getUpdatedVersion($this->objectLibraryID);
     // Set of tags matching name
     if ($name && $this->subset != 'tags') {
         $this->allowMethods(array('GET'));
         $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $name);
         if (!$tagIDs) {
             $this->e404();
         }
         $title = "Tags matching ‘" . $name . "’";
     } else {
         $this->allowMethods(array('GET', 'DELETE'));
         if ($this->scopeObject) {
             $this->allowMethods(array('GET'));
             switch ($this->scopeObject) {
                 case 'collections':
                     $collection = Zotero_Collections::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                     if (!$collection) {
                         $this->e404();
                     }
                     $title = "Tags in Collection ‘" . $collection->name . "’";
                     $counts = $collection->getTagItemCounts();
                     $tagIDs = array();
                     if ($counts) {
                         foreach ($counts as $tagID => $count) {
                             $tagIDs[] = $tagID;
                             $fixedValues[$tagID] = array('numItems' => $count);
                         }
                     }
                     break;
                 case 'items':
                     $item = Zotero_Items::getByLibraryAndKey($this->objectLibraryID, $this->scopeObjectKey);
                     if (!$item) {
                         $this->e404();
                     }
                     $title = "Tags of '" . $item->getDisplayTitle() . "'";
                     $tagIDs = $item->getTags(true);
                     break;
                 default:
                     throw new Exception("Invalid tags scope object '{$this->scopeObject}'");
             }
         } else {
             if ($this->method == 'DELETE') {
                 // Filter for specific tags with "?tag=foo || bar"
                 $tagNames = !empty($this->queryParams['tag']) ? explode(' || ', $this->queryParams['tag']) : array();
                 Zotero_DB::beginTransaction();
                 foreach ($tagNames as $tagName) {
                     $tagIDs = Zotero_Tags::getIDs($this->objectLibraryID, $tagName);
                     foreach ($tagIDs as $tagID) {
                         $tag = Zotero_Tags::get($this->objectLibraryID, $tagID, true);
                         Zotero_Tags::delete($this->objectLibraryID, $tag->key);
                     }
                 }
                 Zotero_DB::commit();
                 $this->e204();
             } else {
                 $title = "Tags";
                 $results = Zotero_Tags::search($this->objectLibraryID, $this->queryParams);
             }
         }
     }
     if ($tagIDs) {
         $this->queryParams['tagIDs'] = $tagIDs;
         $results = Zotero_Tags::search($this->objectLibraryID, $this->queryParams);
     }
     $this->generateMultiResponse($results, $title, $fixedValues);
     $this->end();
 }