示例#1
0
 public function testLastInsertIDFromStatement()
 {
     Zotero_DB::query("CREATE TABLE test (foo INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, foo2 INTEGER NOT NULL)");
     $sql = "INSERT INTO test VALUES (NULL, ?)";
     $stmt = Zotero_DB::getStatement($sql, true);
     $insertID = Zotero_DB::queryFromStatement($stmt, array(1));
     $this->assertEquals($insertID, 1);
     $insertID = Zotero_DB::queryFromStatement($stmt, array(2));
     $this->assertEquals($insertID, 2);
 }
示例#2
0
 private static function loadItems($libraryID, $itemIDs = array())
 {
     $shardID = Zotero_Shards::getByLibraryID($libraryID);
     $sql = self::getPrimaryDataSQL() . "1";
     // TODO: optimize
     if ($itemIDs) {
         foreach ($itemIDs as $itemID) {
             if (!is_int($itemID)) {
                 throw new Exception("Invalid itemID {$itemID}");
             }
         }
         $sql .= ' AND itemID IN (' . implode(',', array_fill(0, sizeOf($itemIDs), '?')) . ')';
     }
     $stmt = Zotero_DB::getStatement($sql, "loadItems_" . sizeOf($itemIDs), $shardID);
     $itemRows = Zotero_DB::queryFromStatement($stmt, $itemIDs);
     $loadedItemIDs = array();
     if ($itemRows) {
         foreach ($itemRows as $row) {
             if ($row['libraryID'] != $libraryID) {
                 throw new Exception("Item {$itemID} isn't in library {$libraryID}", Z_ERROR_OBJECT_LIBRARY_MISMATCH);
             }
             $itemID = $row['id'];
             $loadedItemIDs[] = $itemID;
             // Item isn't loaded -- create new object and stuff in array
             if (!isset(self::$objectCache[$itemID])) {
                 $item = new Zotero_Item();
                 $item->loadFromRow($row, true);
                 self::$objectCache[$itemID] = $item;
             } else {
                 self::$objectCache[$itemID]->loadFromRow($row, true);
             }
         }
     }
     if (!$itemIDs) {
         // If loading all items, remove old items that no longer exist
         $ids = array_keys(self::$objectCache);
         foreach ($ids as $id) {
             if (!in_array($id, $loadedItemIDs)) {
                 throw new Exception("Unimplemented");
                 //$this->unload($id);
             }
         }
     }
 }
示例#3
0
	protected function loadCreators($reload = false) {
		if ($this->loaded['creators'] && !$reload) return;
		
		if (!$this->id) {
			trigger_error('Item ID not set for item before attempting to load creators', E_USER_ERROR);
		}
		
		if (!is_numeric($this->id)) {
			trigger_error("Invalid itemID '$this->id'", E_USER_ERROR);
		}
		
		if ($this->cacheEnabled) {
			$cacheVersion = 1;
			$cacheKey = $this->getCacheKey("itemCreators", $cacheVersion);
			$creators = Z_Core::$MC->get($cacheKey);
		}
		else {
			$creators = false;
		}
		if ($creators === false) {
			$sql = "SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators
					WHERE itemID=? ORDER BY orderIndex";
			$stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
			$creators = Zotero_DB::queryFromStatement($stmt, $this->id);
			
			if ($this->cacheEnabled) {
				Z_Core::$MC->set($cacheKey, $creators ? $creators : array());
			}
		}
		
		$this->creators = [];
		$this->loaded['creators'] = true;
		$this->clearChanged('creators');
		
		if (!$creators) {
			return;
		}
		
		foreach ($creators as $creator) {
			$creatorObj = Zotero_Creators::get($this->libraryID, $creator['creatorID'], true);
			if (!$creatorObj) {
				Z_Core::$MC->delete($cacheKey);
				throw new Exception("Creator {$creator['creatorID']} not found");
			}
			$this->creators[$creator['orderIndex']] = array(
				'creatorTypeID' => $creator['creatorTypeID'],
				'ref' => $creatorObj
			);
		}
	}
示例#4
0
 private function loadCreators()
 {
     if (!$this->id) {
         trigger_error('Item ID not set for item before attempting to load creators', E_USER_ERROR);
     }
     if (!is_numeric($this->id)) {
         trigger_error("Invalid itemID '{$this->id}'", E_USER_ERROR);
     }
     $cacheKey = $this->getCacheKey("itemCreators");
     $creators = Z_Core::$MC->get($cacheKey);
     //var_dump($creators);
     if ($creators === false) {
         $sql = "SELECT creatorID, creatorTypeID, orderIndex FROM itemCreators\n\t\t\t\t\tWHERE itemID=? ORDER BY orderIndex";
         $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
         $creators = Zotero_DB::queryFromStatement($stmt, $this->id);
         Z_Core::$MC->set($cacheKey, $creators ? $creators : array());
     }
     $this->creators = array();
     $this->loaded['creators'] = true;
     if (!$creators) {
         return;
     }
     foreach ($creators as $creator) {
         $creatorObj = Zotero_Creators::get($this->libraryID, $creator['creatorID'], true);
         if (!$creatorObj) {
             Z_Core::$MC->delete($cacheKey);
             throw new Exception("Creator {$creator['creatorID']} not found");
         }
         $this->creators[$creator['orderIndex']] = array('creatorTypeID' => $creator['creatorTypeID'], 'ref' => $creatorObj);
     }
 }
示例#5
0
 public function save($full = false)
 {
     if (!$this->libraryID) {
         trigger_error("Library ID must be set before saving", E_USER_ERROR);
     }
     Zotero_Tags::editCheck($this);
     if (!$this->changed) {
         Z_Core::debug("Tag {$this->id} has not changed");
         return false;
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     try {
         $tagID = $this->id ? $this->id : Zotero_ID::get('tags');
         $isNew = !$this->id;
         Z_Core::debug("Saving tag {$tagID}");
         $key = $this->key ? $this->key : $this->generateKey();
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $dateAdded = $this->dateAdded ? $this->dateAdded : $timestamp;
         $dateModified = $this->dateModified ? $this->dateModified : $timestamp;
         $fields = "name=?, `type`=?, dateAdded=?, dateModified=?,\n\t\t\t\tlibraryID=?, `key`=?, serverDateModified=?";
         $params = array($this->name, $this->type ? $this->type : 0, $dateAdded, $dateModified, $this->libraryID, $key, $timestamp);
         try {
             if ($isNew) {
                 $sql = "INSERT INTO tags SET tagID=?, {$fields}";
                 $stmt = Zotero_DB::getStatement($sql, true, $shardID);
                 Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params));
                 // Remove from delete log if it's there
                 $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='tag' AND `key`=?";
                 Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
             } else {
                 $sql = "UPDATE tags SET {$fields} WHERE tagID=?";
                 $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
                 Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID)));
             }
         } catch (Exception $e) {
             // If an incoming tag is the same as an existing tag, but with a different key,
             // then delete the old tag and add its linked items to the new tag
             if (preg_match("/Duplicate entry .+ for key 'uniqueTags'/", $e->getMessage())) {
                 // GET existing tag
                 $existing = Zotero_Tags::getIDs($this->libraryID, $this->name);
                 if (!$existing) {
                     throw new Exception("Existing tag not found");
                 }
                 foreach ($existing as $id) {
                     $tag = Zotero_Tags::get($this->libraryID, $id, true);
                     if ($tag->__get('type') == $this->type) {
                         $linked = $tag->getLinkedItems(true);
                         Zotero_Tags::delete($this->libraryID, $tag->key);
                         break;
                     }
                 }
                 // Save again
                 if ($isNew) {
                     $sql = "INSERT INTO tags SET tagID=?, {$fields}";
                     $stmt = Zotero_DB::getStatement($sql, true, $shardID);
                     Zotero_DB::queryFromStatement($stmt, array_merge(array($tagID), $params));
                     // Remove from delete log if it's there
                     $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='tag' AND `key`=?";
                     Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
                 } else {
                     $sql = "UPDATE tags SET {$fields} WHERE tagID=?";
                     $stmt = Zotero_DB::getStatement($sql, true, Zotero_Shards::getByLibraryID($this->libraryID));
                     Zotero_DB::queryFromStatement($stmt, array_merge($params, array($tagID)));
                 }
                 $new = array_unique(array_merge($linked, $this->getLinkedItems(true)));
                 $this->setLinkedItems($new);
             } else {
                 throw $e;
             }
         }
         // Linked items
         if ($full || !empty($this->changed['linkedItems'])) {
             $removed = array();
             $newids = array();
             $currentIDs = $this->getLinkedItems(true);
             if (!$currentIDs) {
                 $currentIDs = array();
             }
             if ($full) {
                 $sql = "SELECT itemID FROM itemTags WHERE tagID=?";
                 $stmt = Zotero_DB::getStatement($sql, true, $shardID);
                 $dbItemIDs = Zotero_DB::columnQueryFromStatement($stmt, $tagID);
                 if ($dbItemIDs) {
                     $removed = array_diff($dbItemIDs, $currentIDs);
                     $newids = array_diff($currentIDs, $dbItemIDs);
                 } else {
                     $newids = $currentIDs;
                 }
             } else {
                 if ($this->previousData['linkedItems']) {
                     $removed = array_diff($this->previousData['linkedItems'], $currentIDs);
                     $newids = array_diff($currentIDs, $this->previousData['linkedItems']);
                 } else {
                     $newids = $currentIDs;
                 }
             }
             if ($removed) {
                 $sql = "DELETE FROM itemTags WHERE tagID=? AND itemID IN (";
                 $q = array_fill(0, sizeOf($removed), '?');
                 $sql .= implode(', ', $q) . ")";
                 Zotero_DB::query($sql, array_merge(array($this->id), $removed), $shardID);
             }
             if ($newids) {
                 $newids = array_values($newids);
                 $sql = "INSERT INTO itemTags (tagID, itemID) VALUES ";
                 $maxInsertGroups = 50;
                 Zotero_DB::bulkInsert($sql, $newids, $maxInsertGroups, $tagID, $shardID);
             }
             //Zotero.Notifier.trigger('add', 'collection-item', $this->id . '-' . $itemID);
         }
         Zotero_DB::commit();
         Zotero_Tags::cachePrimaryData(array('id' => $tagID, 'libraryID' => $this->libraryID, 'key' => $key, 'name' => $this->name, 'type' => $this->type ? $this->type : 0, 'dateAdded' => $dateAdded, 'dateModified' => $dateModified));
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     // If successful, set values in object
     if (!$this->id) {
         $this->id = $tagID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     $this->init();
     if ($isNew) {
         Zotero_Tags::cache($this);
         Zotero_Tags::cacheLibraryKeyID($this->libraryID, $key, $tagID);
     }
     return $this->id;
 }
示例#6
0
 public function save($fixGaps = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID must be set before saving");
     }
     Zotero_Searches::editCheck($this);
     if (!$this->changed) {
         Z_Core::debug("Search {$this->id} has not changed");
         return false;
     }
     if (!isset($this->name) || $this->name === '') {
         throw new Exception("Name not provided for saved search");
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     $isNew = !$this->id || !$this->exists();
     try {
         $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches');
         Z_Core::debug("Saving search {$this->id}");
         if (!$isNew) {
             $sql = "DELETE FROM savedSearchConditions WHERE searchID=?";
             Zotero_DB::query($sql, $searchID, $shardID);
         }
         $key = $this->key ? $this->key : $this->generateKey();
         $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?";
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp);
         $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
         if ($isNew) {
             $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params));
             Zotero_Searches::cacheLibraryKeyID($this->libraryID, $key, $searchID);
             // Remove from delete log if it's there
             $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?";
             Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
         } else {
             $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID)));
         }
         // Close gaps in savedSearchIDs
         $saveConditions = array();
         $i = 1;
         foreach ($this->conditions as $id => $condition) {
             if (!$fixGaps && $id != $i) {
                 trigger_error('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' . $this->id, E_USER_ERROR);
             }
             $saveConditions[$i] = $condition;
             $i++;
         }
         $this->conditions = $saveConditions;
         // TODO: use proper bound parameters once DB class is updated
         foreach ($this->conditions as $searchConditionID => $condition) {
             $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)";
             $sqlParams = array($searchID, $searchConditionID, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', $condition['required'] ? 1 : 0);
             try {
                 Zotero_DB::query($sql, $sqlParams, $shardID);
             } catch (Exception $e) {
                 $msg = $e->getMessage();
                 if (strpos($msg, "Data too long for column 'value'") !== false) {
                     throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'");
                 }
                 throw $e;
             }
         }
         Zotero_DB::commit();
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     // If successful, set values in object
     if (!$this->id) {
         $this->id = $searchID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     return $this->id;
 }
示例#7
0
 public function save($userID = false)
 {
     if (!$this->libraryID) {
         throw new Exception("Library ID must be set before saving");
     }
     Zotero_Searches::editCheck($this, $userID);
     if (!$this->changed) {
         Z_Core::debug("Search {$this->id} has not changed");
         return false;
     }
     if (!isset($this->name) || $this->name === '') {
         throw new Exception("Name not provided for saved search");
     }
     $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
     Zotero_DB::beginTransaction();
     $isNew = !$this->id || !$this->exists();
     try {
         $searchID = $this->id ? $this->id : Zotero_ID::get('savedSearches');
         Z_Core::debug("Saving search {$this->id}");
         if (!$isNew) {
             $sql = "DELETE FROM savedSearchConditions WHERE searchID=?";
             Zotero_DB::query($sql, $searchID, $shardID);
         }
         $key = $this->key ? $this->key : Zotero_ID::getKey();
         $fields = "searchName=?, libraryID=?, `key`=?, dateAdded=?, dateModified=?,\n\t\t\t\t\t\tserverDateModified=?, version=?";
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $params = array($this->name, $this->libraryID, $key, $this->dateAdded ? $this->dateAdded : $timestamp, $this->dateModified ? $this->dateModified : $timestamp, $timestamp, Zotero_Libraries::getUpdatedVersion($this->libraryID));
         $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
         if ($isNew) {
             $sql = "INSERT INTO savedSearches SET searchID=?, {$fields}";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge(array($searchID), $params));
             // Remove from delete log if it's there
             $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='search' AND `key`=?";
             Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
         } else {
             $sql = "UPDATE savedSearches SET {$fields} WHERE searchID=?";
             $stmt = Zotero_DB::getStatement($sql, true, $shardID);
             Zotero_DB::queryFromStatement($stmt, array_merge($params, array($searchID)));
         }
         foreach ($this->conditions as $searchConditionID => $condition) {
             $sql = "INSERT INTO savedSearchConditions (searchID,\n\t\t\t\t\t\tsearchConditionID, `condition`, mode, operator,\n\t\t\t\t\t\tvalue, required) VALUES (?,?,?,?,?,?,?)";
             $sqlParams = array($searchID, $searchConditionID + 1, $condition['condition'], $condition['mode'] ? $condition['mode'] : '', $condition['operator'] ? $condition['operator'] : '', $condition['value'] ? $condition['value'] : '', !empty($condition['required']) ? 1 : 0);
             try {
                 Zotero_DB::query($sql, $sqlParams, $shardID);
             } catch (Exception $e) {
                 $msg = $e->getMessage();
                 if (strpos($msg, "Data too long for column 'value'") !== false) {
                     throw new Exception("=Value '" . mb_substr($condition['value'], 0, 75) . "…' too long in saved search '" . $this->name . "'");
                 }
                 throw $e;
             }
         }
         Zotero_DB::commit();
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     if (!$this->id) {
         $this->id = $searchID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     return $this->id;
 }
示例#8
0
 public function save()
 {
     if (!$this->libraryID) {
         trigger_error("Library ID must be set before saving", E_USER_ERROR);
     }
     Zotero_Creators::editCheck($this);
     // If empty, move on
     if ($this->firstName === '' && $this->lastName === '') {
         throw new Exception('First and last name are empty');
     }
     if ($this->fieldMode == 1 && $this->firstName !== '') {
         throw new Exception('First name must be empty in single-field mode');
     }
     if (!$this->hasChanged()) {
         Z_Core::debug("Creator {$this->id} has not changed");
         return false;
     }
     Zotero_DB::beginTransaction();
     try {
         $creatorID = $this->id ? $this->id : Zotero_ID::get('creators');
         $isNew = !$this->id;
         Z_Core::debug("Saving creator {$this->id}");
         $key = $this->key ? $this->key : $this->generateKey();
         $timestamp = Zotero_DB::getTransactionTimestamp();
         $dateAdded = $this->dateAdded ? $this->dateAdded : $timestamp;
         $dateModified = $this->changed['dateModified'] ? $this->dateModified : $timestamp;
         $fields = "firstName=?, lastName=?, fieldMode=?,\n\t\t\t\t\t\tlibraryID=?, `key`=?, dateAdded=?, dateModified=?, serverDateModified=?";
         $params = array($this->firstName, $this->lastName, $this->fieldMode, $this->libraryID, $key, $dateAdded, $dateModified, $timestamp);
         $shardID = Zotero_Shards::getByLibraryID($this->libraryID);
         try {
             if ($isNew) {
                 $sql = "INSERT INTO creators SET creatorID=?, {$fields}";
                 $stmt = Zotero_DB::getStatement($sql, true, $shardID);
                 Zotero_DB::queryFromStatement($stmt, array_merge(array($creatorID), $params));
                 // Remove from delete log if it's there
                 $sql = "DELETE FROM syncDeleteLogKeys WHERE libraryID=? AND objectType='creator' AND `key`=?";
                 Zotero_DB::query($sql, array($this->libraryID, $key), $shardID);
             } else {
                 $sql = "UPDATE creators SET {$fields} WHERE creatorID=?";
                 $stmt = Zotero_DB::getStatement($sql, true, $shardID);
                 Zotero_DB::queryFromStatement($stmt, array_merge($params, array($creatorID)));
             }
         } catch (Exception $e) {
             if (strpos($e->getMessage(), " too long") !== false) {
                 if (strlen($this->firstName) > 255) {
                     throw new Exception("=First name '" . mb_substr($this->firstName, 0, 50) . "…' too long");
                 }
                 if (strlen($this->lastName) > 255) {
                     if ($this->fieldMode == 1) {
                         throw new Exception("=Last name '" . mb_substr($this->lastName, 0, 50) . "…' too long");
                     } else {
                         throw new Exception("=Name '" . mb_substr($this->lastName, 0, 50) . "…' too long");
                     }
                 }
             }
             throw $e;
         }
         // The client updates the mod time of associated items here, but
         // we don't, because either A) this is from syncing, where appropriate
         // mod times come from the client or B) the change is made through
         // $item->setCreator(), which updates the mod time.
         //
         // If the server started to make other independent creator changes,
         // linked items would need to be updated.
         Zotero_DB::commit();
         Zotero_Creators::cachePrimaryData(array('id' => $creatorID, 'libraryID' => $this->libraryID, 'key' => $key, 'dateAdded' => $dateAdded, 'dateModified' => $dateModified, 'firstName' => $this->firstName, 'lastName' => $this->lastName, 'fieldMode' => $this->fieldMode));
     } catch (Exception $e) {
         Zotero_DB::rollback();
         throw $e;
     }
     // If successful, set values in object
     if (!$this->id) {
         $this->id = $creatorID;
     }
     if (!$this->key) {
         $this->key = $key;
     }
     $this->init();
     if ($isNew) {
         Zotero_Creators::cache($this);
         Zotero_Creators::cacheLibraryKeyID($this->libraryID, $key, $creatorID);
     }
     // TODO: invalidate memcache?
     return $this->id;
 }
示例#9
0
    private static function loadItems($libraryID, $itemIDs = array())
    {
        $shardID = Zotero_Shards::getByLibraryID($libraryID);
        $sql = 'SELECT I.*,
				(SELECT COUNT(*) FROM itemNotes INo
					WHERE sourceItemID=I.itemID AND INo.itemID NOT IN
					(SELECT itemID FROM deletedItems)) AS numNotes,
				(SELECT COUNT(*) FROM itemAttachments IA
					WHERE sourceItemID=I.itemID AND IA.itemID NOT IN
					(SELECT itemID FROM deletedItems)) AS numAttachments	
			FROM items I WHERE 1';
        // TODO: optimize
        if ($itemIDs) {
            foreach ($itemIDs as $itemID) {
                if (!is_int($itemID)) {
                    throw new Exception("Invalid itemID {$itemID}");
                }
            }
            $sql .= ' AND I.itemID IN (' . implode(',', array_fill(0, sizeOf($itemIDs), '?')) . ')';
        }
        $stmt = Zotero_DB::getStatement($sql, "loadItems_" . sizeOf($itemIDs), $shardID);
        $itemRows = Zotero_DB::queryFromStatement($stmt, $itemIDs);
        $loadedItemIDs = array();
        if ($itemRows) {
            foreach ($itemRows as $row) {
                if ($row['libraryID'] != $libraryID) {
                    throw new Exception("Item {$itemID} isn't in library {$libraryID}", Z_ERROR_OBJECT_LIBRARY_MISMATCH);
                }
                $itemID = $row['itemID'];
                $loadedItemIDs[] = $itemID;
                // Item isn't loaded -- create new object and stuff in array
                if (!isset(self::$itemsByID[$itemID])) {
                    $item = new Zotero_Item();
                    $item->loadFromRow($row, true);
                    self::$itemsByID[$itemID] = $item;
                } else {
                    self::$itemsByID[$itemID]->loadFromRow($row, true);
                }
            }
        }
        if (!$itemIDs) {
            // If loading all items, remove old items that no longer exist
            $ids = array_keys(self::$itemsByID);
            foreach ($ids as $id) {
                if (!in_array($id, $loadedItemIDs)) {
                    throw new Exception("Unimplemented");
                    //$this->unload($id);
                }
            }
            /*
            _cachedFields = ['itemID', 'itemTypeID', 'dateAdded', 'dateModified',
            	'numNotes', 'numAttachments', 'numChildren'];
            */
            //this._reloadCache = false;
        }
    }